弹性滚动和惯性滚动效果的实现原理——CoordinatorLay
前面介绍了OverScroll的使用,没看过文章的同学可以先了解下《类似微信首页弹性滚动和惯性滚动效果的实现——OverScroll》
接下来介绍OverScroll的实现原理。
CoordinatorLayout
CoordinatorLayout
是在Support 包中功能强大的布局容器,它本质是一个 FrameLayout,然而它允许开发者通过自定义Behavior
协调各个子view,实现各种复杂酷炫的UI交互效果。
使用CoordinatorLayout
需要在 build.gradle 加入:
implementation 'com.android.support:design:26.1.0'
网上很多关于CoordinatorLayout
的入门文章,这里笔者不再赘述,所谓实践大于理论,本文讲述如何利用CoordinatorLayout+Behavior实现弹性滑动和惯性滑动,从侧面去理解它的使用原理.
本文实现类似微信首页的弹性滑动和惯性滑动效果,支持水平和垂直方向上的滚动,如下图所示:
vertical over-scroll[图片上传失败...(image-be99c9-1554176574416)]
Behavior
CoordinatorLayout
主要是通过Behavior
来协调子view,这里涉及到的Behavior
的关键方法如下:
方法 | 描述 |
---|---|
boolean onStartNestedScroll(CoordinatorLayout parent, View child, View directTargetChild, View target, int nestedScrollAxes, int type) | 根据返回值判断是否要处理target 发生的滑动,一般用于判断是否要处理某个方向上的滑动.例如 return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 表示处理垂直方向方向上的滑动.接下来的滑动事件将回调给下面的方法处理. |
void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed, int type) | 当target 即将发生滑动时调用,在这里可以做拦截处理.可以修改参数consumed 表示消耗(拦截)了多少像素。例如target 控件本身想要垂直方向上滑动100px,而我们需要拦截掉80px,则要设置 consumed[1] = 80 ,(consumed[0] 、consumed[1] 分别对应x轴和y轴),最后target 控件实际只滑动了20px. |
void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) |
target 控件发生滑动后调用, dyConsumed为实际消耗的距离,dyUnconsumed为未消耗的距离.例如上面的滑动,此时dyConsumed = 20 ,dxUnconsumed = 0 ,如果dyConsumed = 15 ,dxUnconsumed = 5 则表示target 在滑动15px距离时到达了边界,我们可以利用dxUnconsumed 处理一些越界后的滑动. |
boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) | 用户快速滑动target 并松开手指发生惯性滑动之前调用,返回true 表示拦截该惯性滑动事件. |
void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int type) | 所有的滑动停止后调用. |
原理分析
弹性滑动和惯性滑动都属于过度滑动(Over scroll),即在达到正常滑动范围的边界后继续滑动.因此,我们只需要处理在达到边界时的越界滑动效果.关键的处理逻辑如下:
overscoll上图描述了向下滑动过程中需要处理的关键逻辑,同理,向上滑动也采取类似的处理.
另外我们还要处理惯性滑动,当快速滑动产生fling事件时,让列表滑到边界时仍能够惯性滑动一点距离.这里需要借助系统提供的工具类OverScroller
, 主要在onNestedPreFling
里相关的惯性滑动的参数传入OverScroller
.
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
if (child == target) {
mOverScroller.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
后面便可以在滑动过程中通过mOverScroller.getCurrVelocity()
获取当前时间惯性滑动的速度,当速度小于某个值时则停止滑动.
在Behavior
中的关键方法的参数中基本上最后都有个type值,文档解释为the type of input which cause this scroll event
,即表示产生当前滑动的事件来源,当type == ViewCompat.TYPE_TOUCH
时表示由用户触摸控件产生的滑动,type == ViewCompat.TYPE_NON_TOUCH
时表示由非触摸产生的滑动,比如惯性造成的滑动就是非触摸产生的.
因此我们在滑动过程中可以通过type判断当前滑动是否为惯性滑动.
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
int type) {
if (type == ViewCompat.TYPE_TOUCH) { // scroll
} else { // fling
}
}
最后,我们需要在停滑动时,如果列表发生了越界偏移,则需要把列表弹回原位,这里通过ValueAnimator动画实现即可.
代码实现
-
弹性滑动和惯性滑动过程中需要一些参数控制滑动效果,如最大的滑动距离,惯性滑动的最小速度,滑动的阻尼因子等,因此我们需要定义一个接口,和'Behavior`绑定的子View必须实现该接口,接口定义请查看IOverScrollCallback,默认实现为SimpleOverScrollCallback.
-
自定义Behavior弹性滑动和惯性滑动,基类为BaseOverScrollBehavior,控制垂直滚动OverScrollVerticalBehavior,控制水平滚动OverScrollHorizontalBehavior.
-
让NestedScrolling滑动控件(如
RecyclerView
,NestedScrollView
等)实现IOverScrollCallback
,提供相关滑动参数,这里以OverScrollScrollView
控件为例,代码请查看OverScrollScrollView.
效果(布局相关:NestedScrollFragment ,layout_scrollview.xml):
nested over-scroll项目地址OverScroll
多谢支持我的github项目>>>OverScroll!