简介

使用侧滑Activity返回很常见,例如微信就用到了。那么它是怎么实现的呢。本文带你剖析一下实现原理。我在github上找了一个star有2.6k的开源,我们分析他是怎么实现的

  1. //star 2.6k
  2. 'com.r0adkll:slidableactivity:2.0.5'

Slidr使用示例

它的使用很简单,首先要设置透明的窗口背景

  1. <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  2. <!-- Customize your theme here. -->
  3. <item name="android:textAllCaps">false</item>
  4. <item name="android:windowActionBar">false</item>
  5. <item name="windowActionBar">false</item>
  6. <item name="windowNoTitle">true</item>
  7. <item name="colorPrimary">@color/colorPrimary</item>
  8. <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
  9. <item name="colorAccent">@color/colorAccent</item>
  10. <item name="android:windowIsTranslucent">true</item>
  11. <item name="android:windowBackground">@android:color/transparent</item>
  12. </style>

然后

  1. //setContent(View view)后
  2. Slidr.attach(this);

下面可以从三个步骤看其原理

步骤一 重新包裹界面

Slidr.class

  1. public static SlidrInterface attach(final Activity activity, final int statusBarColor1, final int statusBarColor2){
  2. //0 创建滑动嵌套界面SliderPanel
  3. final SliderPanel panel = initSliderPanel(activity, null);
  4. //7 Set the panel slide listener for when it becomes closed or opened
  5. // 监听回调
  6. panel.setOnPanelSlideListener(new SliderPanel.OnPanelSlideListener() {
  7. ...
  8. //open close等
  9. });
  10. // Return the lock interface
  11. return initInterface(panel);
  12. }
  13. private static SliderPanel initSliderPanel(final Activity activity, final SlidrConfig config) {
  14. //3 获取decorview
  15. ViewGroup decorView = (ViewGroup)activity.getWindow().getDecorView();
  16. //4 获取我们布局的内容并删除
  17. View oldScreen = decorView.getChildAt(0);
  18. decorView.removeViewAt(0);
  19. //5 Setup the slider panel and attach it to the decor
  20. // 建立滑动嵌套视图SliderPanel并且添加到DecorView中
  21. SliderPanel panel = new SliderPanel(activity, oldScreen, config);
  22. panel.setId(R.id.slidable_panel);
  23. oldScreen.setId(R.id.slidable_content);
  24. //6 把我们的界面布局添加到SliderPanel,并且把SliderPanel添加到decorView中
  25. panel.addView(oldScreen);
  26. decorView.addView(panel, 0);
  27. return panel;
  28. }

步骤二 使用ViewDragHelper.class处理滑动手势

SliderPanel.class

  1. private void init(){
  2. ...
  3. //1 ViewDragHelper创建
  4. mDragHelper = ViewDragHelper.create(this, mConfig.getSensitivity(), callback);
  5. mDragHelper.setMinVelocity(minVel);
  6. mDragHelper.setEdgeTrackingEnabled(mEdgePosition);
  7. //2 Setup the dimmer view 添加用于指示滑动过程的View到底层
  8. mDimView = new View(getContext());
  9. mDimView.setBackgroundColor(mConfig.getScrimColor());
  10. mDimView.setAlpha(mConfig.getScrimStartAlpha());
  11. addView(mDimView);
  12. }

步骤三 在ViewDragHelper.Callback中处理我们的界面的拖动

我们首先明确ViewDragHelper仅仅是处理ParentView与它子View的关系,不会一直遍历到最顶层的View。ViewDragHelper的捕获capture是这样实现的

  1. @Nullable
  2. public View findTopChildUnder(int x, int y) {
  3. final int childCount = mParentView.getChildCount();
  4. for (int i = childCount - 1; i >= 0; i--) {
  5. final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i));
  6. if (x >= child.getLeft() && x < child.getRight()
  7. && y >= child.getTop() && y < child.getBottom()) {
  8. return child;
  9. }
  10. }
  11. return null;
  12. }

重点在SliderPanel.class的ViewDragHelper.Callback callback的实现,作者实现实现了很多个方向的滑动处理mLeftCallback、mRightCallback、mTopCallback、mBottomCallback、mVerticalCallback、mHorizontalCallback, 我们取mLeftCallback来分析

  1. private ViewDragHelper.Callback mLeftCallback = new ViewDragHelper.Callback() {
  2. //捕获View
  3. @Override
  4. public boolean tryCaptureView(View child, int pointerId) {
  5. boolean edgeCase = !mConfig.isEdgeOnly() || mDragHelper.isEdgeTouched(mEdgePosition, pointerId);
  6. //像前面说的,我们的内容是最上层子View,mDecorView这里指的是我们的contentView
  7. return child.getId() == mDecorView.getId() && edgeCase;
  8. }
  9. //拖动, 最终是通过view.offsetLeftAndRight(offset)实现移动
  10. @Override
  11. public int clampViewPositionHorizontal(View child, int left, int dx) {
  12. return clamp(left, 0, mScreenWidth);
  13. }
  14. //滑动范围
  15. @Override
  16. public int getViewHorizontalDragRange(View child) {
  17. return mScreenWidth;
  18. }
  19. //释放处理,判断是滚回屏幕
  20. @Override
  21. public void onViewReleased(View releasedChild, float xvel, float yvel) {
  22. super.onViewReleased(releasedChild, xvel, yvel);
  23. int left = releasedChild.getLeft();
  24. int settleLeft = 0;
  25. int leftThreshold = (int) (getWidth() * mConfig.getDistanceThreshold());
  26. boolean isVerticalSwiping = Math.abs(yvel) > mConfig.getVelocityThreshold();
  27. if(xvel > 0){
  28. if(Math.abs(xvel) > mConfig.getVelocityThreshold() && !isVerticalSwiping){
  29. settleLeft = mScreenWidth;
  30. }else if(left > leftThreshold){
  31. settleLeft = mScreenWidth;
  32. }
  33. }else if(xvel == 0){
  34. if(left > leftThreshold){
  35. settleLeft = mScreenWidth;
  36. }
  37. }
  38. //滚动到left=0(正常布局) 或者 滚动到left=mScreenWidth(滚出屏幕)关闭Activity
  39. mDragHelper.settleCapturedViewAt(settleLeft, releasedChild.getTop());
  40. invalidate();
  41. }
  42. //转换位置百分比,确定指示层的透明度
  43. @Override
  44. public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
  45. super.onViewPositionChanged(changedView, left, top, dx, dy);
  46. float percent = 1f - ((float)left / (float)mScreenWidth);
  47. if(mListener != null) mListener.onSlideChange(percent);
  48. // Update the dimmer alpha
  49. applyScrim(percent);
  50. }
  51. //回调到Slidr处理Activity状态
  52. @Override
  53. public void onViewDragStateChanged(int state) {
  54. super.onViewDragStateChanged(state);
  55. if(mListener != null) mListener.onStateChanged(state);
  56. switch (state){
  57. case ViewDragHelper.STATE_IDLE:
  58. if(mDecorView.getLeft() == 0){
  59. // State Open
  60. if(mListener != null) mListener.onOpened();
  61. }else{
  62. // State Closed 这里回调到Slidr处理activity.finish()
  63. if(mListener != null) mListener.onClosed();
  64. }
  65. break;
  66. case ViewDragHelper.STATE_DRAGGING:
  67. break;
  68. case ViewDragHelper.STATE_SETTLING:
  69. break;
  70. }
  71. }
  72. };

对于mDragHelper.settleCapturedViewAt(settleLeft, releasedChild.getTop());内部是使用Scroller.class辅助滚动,所以要在SliderPanel中重写View.computeScroll()

  1. @Override
  2. public void computeScroll() {
  3. super.computeScroll();
  4. if(mDragHelper.continueSettling(true)){
  5. ViewCompat.postInvalidateOnAnimation(this);
  6. }
  7. }

总结

整体方案如下图所示

总体来看原理并不复杂, 就是通过ViewDragHelper对View进行拖动。

Activity侧滑返回的实现原理的更多相关文章

  1. 全新的手势,侧滑返回、全局右滑返回都OUT啦!

    前言 Android快速开发框架-ZBLibrary 最近将以前的 全局右滑返回 手势功能改成了 底部左右滑动手势. 为什么呢?为了解决滑动返回手势的问题. 目前有3种滑动返回手势 一.侧滑返回 代表 ...

  2. Android中Activity处理返回结果的实现方式

    大家在网上购物时都有这样一个体验,在确认订单选择收货人以及地址时,会跳转页面到我们存入网站内的所有收货信息(包含收货地址,收货人)的界面供我们选择,一旦我们点击其中某一条信息,则会自动跳转到订单提交界 ...

  3. Activity详解三 启动activity并返回结果

    首先看演示: 1 简介 .如果想在Activity中得到新打开Activity 关闭后返回的数据,需要使用系统提供的startActivityForResult(Intent intent, int ...

  4. 一行代码,让你的应用中UIScrollView的滑动与侧滑返回并存

    侧滑返回是iOS系统的一个很贴心的功能,特别是在大屏手机上,单手操作的时候去按左上角的返回键特别不方便.当我在使用一个APP的时候,如果控制器不能侧滑返回,我会觉得这个APP十分不友好...这款产品在 ...

  5. 【Android 复习】:从Activity中返回数据

    在实际的应用中,我们不仅仅要向Activity传递数据,而且要从Activity中返回数据,虽然返回数据和传递类似,也可以采用上一讲中的四种方式来传递数据,但是一般建议采用Intent对象的方式的来返 ...

  6. Android_打开多个Activity,返回到第一个Activity

    正文 一.流程截图 二.问题说明 依次从登录到三级界面,然后退出回到登录界面. 三.解决办法 3.1 实现代码 三级界面调用如下代码:         Intent intent = new Inte ...

  7. 安卓activity捕获返回button关闭应用的方法

    安卓activity捕获返回button关闭应用的方法 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { //按下键盘 ...

  8. Android - 和其他APP交互 - 获得activity的返回值

    启用另一个activity不一定是单向的.也可以启用另一个activity并且获得返回值.要获得返回值的话,调用startActivityForResult()(而不是startActivity()) ...

  9. iOS7以后的侧滑返回上一页

    我们知道,iOS7以后,导航控制器默认带了侧滑返回功能,但是仅限于屏幕边缘.而且在你自定义leftBarButtonItem等之后侧滑效果就会消失.这种问题怎么解决呢? 首先,我们先来看看系统的这种手 ...

随机推荐

  1. JavaWeb——MySQL基础

    内容索引 数据库的基本概念 MySQL数据库软件 安装 卸载 配置 SQL 数据库的基本概念 1. 数据库的英文单词: DataBase 简称 : DB 2. 什么数据库? * 用于存储和管理数据的仓 ...

  2. NumPy之:ndarray多维数组操作

    NumPy之:ndarray多维数组操作 目录 简介 创建ndarray ndarray的属性 ndarray中元素的类型转换 ndarray的数学运算 index和切片 基本使用 index wit ...

  3. Django(31)模板中常用的过滤器

    模版常用过滤器 在模版中,有时候需要对一些数据进行处理以后才能使用.一般在Python中我们是通过函数的形式来完成的.而在模版中,则是通过过滤器来实现的.过滤器使用的是|来使用. add 将传进来的参 ...

  4. Mac 无密码 SSH 登录服务器

    Mac 无密码 SSH 登录服务器,只需要简单三步,不再需要记住账号密码,快速进入服务器 第一步,生成密钥对 在当前用户下创建.ssh目录 mkdir ~/.ssh 使用命令ssh-keygen生成密 ...

  5. 磁盘IO过高时的处理办法 针对系统中磁盘IO负载过高的指导性操作

    磁盘IO过高时的处理办法 针对系统中磁盘IO负载过高的指导性操作 主要命令:echo deadline > /sys/block/sda/queue/scheduler 注:以下的内容仅是提供参 ...

  6. R语言执行脚本的几种命令

    R CMD BATCH 和 Rscript 使用前都要先添加环境变量 把 C:\Program Files\R\R-3.3.0\bin; 加到"系统变量"的Path 值的最开始 可 ...

  7. 如何访问pod --- service(7)

    一.通过service访问pod 我们不应该期望 Kubernetes Pod 是健壮的,而是要假设 Pod 中的容器很可能因为各种原因发生故障而死掉.Deployment 等 controller ...

  8. linux 详解useradd 命令基本用法

    linux 详解useradd 命令基本用法 时间:2019-03-24 本文章向大家介绍linux 详解useradd 命令基本用法,主要包括linux 详解useradd 命令基本用法使用实例.应 ...

  9. linux进阶之nmtui和nmcli配置网络

    CentOS7配置网络推荐使用NetworkManager服务(不推荐network服务). 图形化方式:nmtui或Applications->System Tools->Setting ...

  10. DOCKER学习_016:Docker镜像仓库和HARBOR的简单安装和管理

    一 镜像仓库介绍 1.1 简介 镜像仓库用于存放 Docker镜像 Docker registry提供镜像仓库服务 一个 Docker registry可以包含多个镜像仓库 仓库分为公共镜像仓库与私有 ...