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相关的调用和回调,处理逻辑基本一致。



更多相关文章

  1. Java层Binder使用(ServiceManager)
  2. Android工程直接调用monkey源码进行压力测试
  3. Android(安卓)Service BroadcastReceiver
  4. 一个常见Android崩溃问题的分析
  5. 全面了解Activity
  6. 4.0.x Launcher启动过程
  7. Android开发工具——ADB(Android(安卓)Debug Bridge) HOST端
  8. Android实现文字滚动播放效果
  9. Android(安卓)跑马灯 文字滚动

随机推荐

  1. 【android】7、五大存储
  2. Android(安卓)Volley框架使用方法详解
  3. Android(安卓)WakeLock解析
  4. android 基于百度地图api获取经纬度
  5. Android——编译系统初始化设置
  6. px、dp和sp,这些单位总结
  7. Android(安卓)记住listView的位置
  8. 对android的android:taskAffinity初识
  9. Android(安卓)Aidl机制
  10. Android点亮屏幕