Github 上滑动返回库比较多,由于实现思路甚至代码一部分库都差不多,所以只挑选了两个实现思路比较不同的库作为研究,分别是 SwipeBackLayout 和 and_swipeback。
除滑动返回功能外,本文还会围绕滑动过程中呈现前一个界面的方案对上述两个库展开分析,其他部分源码细节不予分析。
SwipeBackLayout
由于使用 SwipeBackLayout 库提供的滑动返回能力需要继承于 SwipeBackActivity,所以我们直接从 SwipeBackActivity 开始研究。
|
|
可以看出 SwipeBackActivity 实际上只是 SwipeBackActivityHelper 的代理。
|
|
从 SwipeBackActivityHelper 的代码不难看出它主要的工作是在 Activity 创建的时候将 Activity 的背景修改为透明以及创建 SwipeBackLayout 并将其与 Activity 关联。再来看一下 mSwipeBackLayout.attachToActivity(mActivity) 的逻辑:
|
|
SwipeBackLayout 首先通过 Activity 实例获取当前主题下 windowBackground 的色值,并将其设置为 ContentView 的背景色,然后将 ContentView 添加至 SwipeBackLayout 中并将 SwipeBackLayout 设为当前 Activity 的 ContentView。至此便完成了与
Activity 关联的工作。
滑动处理
开始滑动时,SwipeBackLayout 将滑动事件全部转发至 ViewDragHelper 处理,等待 SwipeBackLayout.ViewDragCallback 中接收到相应回调后执行相关逻辑。
|
|
当 ViewDragHelper 将当前状态修改为 STATE_DRAGGING 后,ViewDragHelper 通过调用 ViewDragCallback 的 clampViewPositionHorizontal 或 clampViewPositionVertical 回调方法设定当前页面的 ContentView
应移动的距离。
|
|
当滑动事件结束后(onViewReleased),SwipeBackLayout 通过判断此前在 onViewPositionChanged 中计算好的滑动率是否达到阈值以执行回弹或退出动画,这样就完成了一次完整的滑动过程。
|
|
显示前一个页面
为了显示前一个 Activity 界面,SwipeBackActivityHelper 在 Activity onCreate 时就将当前 Window 设置为透明背景。
|
|
而单单设置为透明背景并不足以显示上一个 Activity。为了解决这个问题,SwipeBackActivityHelper 在接收到 SwipeListener 的 onEdgeTouch 回调后调用 Utils.convertActivityToTranslucent(mActivity) 来让前一个 Activity 显示出来。
|
|
其中 onEdgeTouch 是在 ViewDragCallback 的 tryCaptureView 回调中被调用的。
|
|
那么为什么 Utils.convertActivityToTranslucent(mActivity) 可以让前一个 Activity 显示呢?我们来看一下相关的逻辑:
|
|
其中反射调用的 convertToTranslucent 方法的 Android 官方注释中写到 “Calling this allows the Activity behind this one to be seen again” 。所以,由于 convertActivityToTranslucent 通过反射调用 Activity 的
convertToTranslucent 方法将 Activity 转为 Translucent(相当于 windowIsTranslucent 值为 true),配合之前的透明背景,那么前一个 Activity 也就能显示出来了。
以上即为 SwipeBackLayout 库为了显示上一界面所做的操作,然而这种方法有其弊端。由于被设为透明背景的 Activity 的前一个 Activity 无法进入 onStop(),从而导致 Activity 的 Window 不能释放部分资源。一旦多个透明背景的 Activity 叠加会出现明显的卡顿现象(毕竟这么多 View
需要绘制)。
and_swipeback
and_swipeback 与 SwipeBackLayout 一样提供了自定义的 Activity 让用户直接继承以集成滑动返回功能。
|
|
可以看到 SwipeBackActivity 先将触摸事件转发给 SwipeBackHelper 处理,如未被消费再交由自身处理。
滑动处理
在触摸事件处理上 and_swipeback 并没有使用 ViewDragHelper,而是让 SwipeBackHelper 直接消费触摸事件。
|
|
当 processTouchEvent 判定当前的触摸事件为滑动事件时,SwipeBackHelper 对自身发送了一个 MSG_ACTION_MOVE 的 Message。接收到该 Message 后,SwipeBackHelper 调用了 onSliding 方法以执行滑动动画。
|
|
与 ViewDragHelper 不同的是,SwipeBackHelper 通过调用 View 的 setX 方法来显示位移效果。最后,当手势结束时,SwipeBackHelper
在滑动距离不足时会执行回弹动画,在滑动距离充足时则会一直滑到尽头再结束当前页面,此处逻辑比较简单,不贴出代码。
显示前一个页面
and_swipeback 对前一个界面的管理主要放在 SwipeBackHelper.ViewManager 中,
|
|
当调用 addViewFromPreviousActivity 时,ViewManager 从 ActivityLifecycleHelper 中获得上一个 Activity 的实例(其中 ActivityLifecycleHelper 实现了 Application.ActivityLifecycleCallbacks 并需在 Application
中注册监听,从而在 Activity 栈发生变化时能够获取到每个 Activity 的实例),并将该 Activity 的 ContentView 添加到当前 Activity 的底部。当滑动时,通过移开上层的 View 则可看到底部的 Previous ContentView。
相比 SwipeBackLayout,and_swipeback 避免了过多透明背景页面时所造成的卡顿感。
总结
在滑动处理上,两个库的能力其实都一样,也不存在与滑动控件冲突的情况。在显示前一个页面的方案上,从性能角度自然是 and_swipeback 更好,但实现逻辑上显然更加复杂。
在选择滑动返回库时,如果所需滑动返回的 Activity 叠加数量并不多,使用 SwipeBackLayout 或 and_swipeback 都差不多。而当带滑动返回功能的 Activity 数量较多时,如之前所述,透明背景会造成明显的卡顿感,此时应当使用 and_swipeback。