Android(安卓)NestedScrolling嵌套滑动机制
AndroidNestedScrolling嵌套滑动机制
最近项目要用到官网的下拉刷新SwipeRefreshLayout,它是个容器,包裹各种控件实现下拉,不像以前自己要实现事件的拦截,都是通过对Touch事件中的三个函数:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
查看其源码里
publicclassSwipeRefreshLayoutextendsViewGroupimplementsNestedScrollingParent,
NestedScrollingChild
原来从AndroidM开始提供一套API来支持嵌入的滑动效果。同样在最新的SupportV4包中也提供了前向的兼容。有了嵌入滑动机制,就能实现很多很复杂的滑动效果。在AndroidDesignSupport库中的CoordinatorLayout组件就是使用了这套机制。
参考的资料
https://segmentfault.com/a/1190000002873657
NestedScrolling的特性可以体现在哪里呢?
比如你使用了Toolbar,下面一个ScrollView,向上滚动隐藏Toolbar,向下滚动显示Toolbar,这里在逻辑上就是一个NestedScrolling——因为你在滚动整个Toolbar在内的View的过程中,又嵌套滚动了里面的ScrollView。
其实以前的事件分发机制有个不好点就是实现起来比较复杂!
新的NestedScrolling机制就很好的解决了这个问题。
NestedScrolling提供了一套父View和子View滑动交互机制。要完成这样的交互,父View需要实现NestedScrollingParent接口,而子View需要实现NestedScrollingChild接口。
我们看看如何实现这个NestedScrolling,首先有几个类(接口)
NestedScrollingChild
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper
以上四个类都在support-v4包中提供,Lollipop的View默认实现了几种方法。
实现接口很简单,这边暂时用到了NestedScrollingChild系列的方法(因为Parent是support-design提供的CoordinatorLayout)
@Override
publicvoidsetNestedScrollingEnabled(booleanenabled){
super.setNestedScrollingEnabled(enabled);
mChildHelper.setNestedScrollingEnabled(enabled);
}
@Override
publicbooleanisNestedScrollingEnabled(){
returnmChildHelper.isNestedScrollingEnabled();
}
@Override
publicbooleanstartNestedScroll(intaxes){
returnmChildHelper.startNestedScroll(axes);
}
@Override
publicvoidstopNestedScroll(){
mChildHelper.stopNestedScroll();
}
@Override
publicbooleanhasNestedScrollingParent(){
returnmChildHelper.hasNestedScrollingParent();
}
@Override
publicbooleandispatchNestedScroll(intdxConsumed,intdyConsumed,intdxUnconsumed,intdyUnconsumed,int[]offsetInWindow){
returnmChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);
}
@Override
publicbooleandispatchNestedPreScroll(intdx,intdy,int[]consumed,int[]offsetInWindow){
returnmChildHelper.dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);
}
@Override
publicbooleandispatchNestedFling(floatvelocityX,floatvelocityY,booleanconsumed){
returnmChildHelper.dispatchNestedFling(velocityX,velocityY,consumed);
}
@Override
publicbooleandispatchNestedPreFling(floatvelocityX,floatvelocityY){
returnmChildHelper.dispatchNestedPreFling(velocityX,velocityY);
}
简单的话你就这么实现就好了。
那么具体我们怎么使用这一套机制呢?比如子View这时候我需要通知父view告诉它我有一个嵌套的touch事件需要我们共同处理。那么针对一个只包含scroll交互,它整个工作流是这样的:
一、startNestedScroll
首先子view需要开启整个流程(内部主要是找到合适的能接受nestedScroll的parent),通知父View,我要和你配合处理TouchEvent
二、dispatchNestedPreScroll
在子View的onInterceptTouchEvent或者onTouch中(一般在MontionEvent.ACTION_MOVE事件里),调用该方法通知父View滑动的距离。该方法的第三第四个参数返回父view消费掉的scroll长度和子View的窗体偏移量。如果这个scroll没有被消费完,则子view进行处理剩下的一些距离,由于窗体进行了移动,如果你记录了手指最后的位置,需要根据第四个参数offsetInWindow计算偏移量,才能保证下一次的touch事件的计算是正确的。
如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。
这个函数一般在子view处理scroll前调用。
三、dispatchNestedScroll
向父view汇报滚动情况,包括子view消费的部分和子view没有消费的部分。
如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。
这个函数一般在子view处理scroll后调用。
四、stopNestedScroll
结束整个流程。
一般是子view发起调用,父view接受回调。
我们最需要关注的是dispatchNestedPreScroll中的consumed参数。
publicbooleandispatchNestedPreScroll(intdx,intdy,int[]consumed,int[]offsetInWindow);
它是一个int型的数组,长度为2,第一个元素是父view消费的x方向的滚动距离;第二个元素是父view消费的y方向的滚动距离,如果这两个值不为0,则子view需要对滚动的量进行一些修正。正因为有了这个参数,使得我们处理滚动事件的时候,思路更加清晰,不会像以前一样被一堆的滚动参数搞混。
实现NestedScrollingChild
首先来说NestedScrollingChild。如果你有一个可以滑动的View,需要被用来作为嵌入滑动的子View,就必须实现本接口。在此View中,包含一个NestedScrollingChildHelper辅助类。NestedScrollingChild接口的实现,基本上就是调用本Helper类的对应的函数即可,因为Helper类中已经实现好了Child和Parent交互的逻辑。原来的View的处理Touch事件,并实现滑动的逻辑大体上不需要改变。
需要做的就是,如果要准备开始滑动了,需要告诉Parent,你要准备进入滑动状态了,调用startNestedScroll()。你在滑动之前,先问一下你的Parent是否需要滑动,也就是调用dispatchNestedPreScroll()。如果父类滑动了一定距离,你需要重新计算一下父类滑动后剩下给你的滑动距离余量。然后,你自己进行余下的滑动。最后,如果滑动距离还有剩余,你就再问一下,Parent是否需要在继续滑动你剩下的距离,也就是调用dispatchNestedScroll()。
实现NestedScrollingParent
作为一个可以嵌入NestedScrollingChild的父View,需要实现NestedScrollingParent,这个接口方法和NestedScrollingChild大致有一一对应的关系。同样,也有一个NestedScrollingParentHelper辅助类来默默的帮助你实现和Child交互的逻辑。滑动动作是Child主动发起,Parent就收滑动回调并作出响应。
从上面的Child分析可知,滑动开始的调用startNestedScroll(),Parent收到onStartNestedScroll()回调,决定是否需要配合Child一起进行处理滑动,如果需要配合,还会回调onNestedScrollAccepted()。
每次滑动前,Child先询问Parent是否需要滑动,即dispatchNestedPreScroll(),这就回调到Parent的onNestedPreScroll(),Parent可以在这个回调中“劫持”掉Child的滑动,也就是先于Child滑动。
Child滑动以后,会调用onNestedScroll(),回调到Parent的onNestedScroll(),这里就是Child滑动后,剩下的给Parent处理,也就是后于Child滑动。
最后,滑动结束,调用onStopNestedScroll()表示本次处理结束。
其实,除了上面的Scroll相关的调用和回调,还有Fling相关的调用和回调,处理逻辑基本一致。
更多相关文章
- Java层Binder使用(ServiceManager)
- Android工程直接调用monkey源码进行压力测试
- Android(安卓)Service BroadcastReceiver
- 一个常见Android崩溃问题的分析
- 全面了解Activity
- 4.0.x Launcher启动过程
- Android开发工具——ADB(Android(安卓)Debug Bridge) HOST端
- Android实现文字滚动播放效果
- Android(安卓)跑马灯 文字滚动