谷歌在发布安卓 Lollipop版本以后,为了更好的用户体验,Google为Android的滑动机制提供了NestedScrolling特性java
好比你使用了Toolbar,下面一个ScrollView,向上滚动隐藏Toolbar,向下滚动显示Toolbar,这里在逻辑上就是一个NestedScrolling —— 由于你在滚动整个Toolbar在内的View的过程当中,又嵌套滚动了里面的ScrollView。android
在这以前,咱们知道Android对Touch事件的分发是有本身一套机制的。主要是有是三个函数:api
dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。数组
这种分发机制让移动应用安全检测平台-爱内测(ineice.com)发现有一个漏洞,据爱内测的CTO介绍:安全
若是子view得到处理touch事件机会的时候,父view就再也没有机会去处理这个touch事件了,直到下一次手指再按下。ide
也就是说,咱们在滑动子View的时候,若是子View对这个滑动事件不想要处理的时候,只能抛弃这个touch事件,而不会把这些传给父view去处理。函数
可是Google新的NestedScrolling机制就很好的解决了这个问题。oop
咱们看看如何实现这个NestedScrolling,首先有几个类(接口)咱们须要关注一下this
NestedScrollingChildspa
NestedScrollingParent
NestedScrollingChildHelper
NestedScrollingParentHelper
以上四个类都在support-v4包中提供,Lollipop的View默认实现了几种方法。
实现接口很简单,这边我暂时用到了NestedScrollingChild系列的方法(由于Parent是support-design提供的CoordinatorLayout)
@Override public void setNestedScrollingEnabled(boolean enabled) { super.setNestedScrollingEnabled(enabled); mChildHelper.setNestedScrollingEnabled(enabled); } @Override public boolean isNestedScrollingEnabled() { return mChildHelper.isNestedScrollingEnabled(); } @Override public boolean startNestedScroll(int axes) { return mChildHelper.startNestedScroll(axes); } @Override public void stopNestedScroll() { mChildHelper.stopNestedScroll(); } @Override public boolean hasNestedScrollingParent() { return mChildHelper.hasNestedScrollingParent(); } @Override public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); } @Override public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); } @Override public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); } @Override public boolean dispatchNestedPreFling(float velocityX, float velocityY) { return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); }
对,简单的话你就这么实现就行了。
这些接口都是咱们在须要的时候本身调用的。childHelper干了些什么事呢?,看一下startNestedScroll方法
/** * Start a new nested scroll for this view. * * <p>This is a delegate method. Call it from your {@link android.view.View View} subclass * method/{@link NestedScrollingChild} interface method with the same signature to implement * the standard policy.</p> * * @param axes Supported nested scroll axes. * See {@link NestedScrollingChild#startNestedScroll(int)}. * @return true if a cooperating parent view was found and nested scrolling started successfully */ public boolean startNestedScroll(int axes) { if (hasNestedScrollingParent()) { // Already in progress return true; } if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; while (p != null) { if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { mNestedScrollingParent = p; ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } return false; }
能够看到这里是帮你实现一些跟NestedScrollingParent交互的一些方法。
ViewParentCompat是一个和父view交互的兼容类,它会判断api version,若是在Lollipop以上,就是用view自带的方法,不然判断是否实现了NestedScrollingParent接口,去调用接口的方法。
那么具体咱们怎么使用这一套机制呢?好比子View这时候我须要通知父view告诉它我有一个嵌套的touch事件须要咱们共同处理。那么针对一个只包含scroll交互,它整个工做流是这样的:
1、startNestedScroll
首先子view须要开启整个流程(内部主要是找到合适的能接受nestedScroll的parent),通知父View,我要和你配合处理TouchEvent
2、dispatchNestedPreScroll
在子View的onInterceptTouchEvent或者onTouch中(通常在MontionEvent.ACTION_MOVE事件里),调用该方法通知父View滑动的距离。该方法的第三第四个参数返回父view消费掉的scroll长度和子View的窗体偏移量。若是这个scroll没有被消费完,则子view进行处理剩下的一些距离,因为窗体进行了移动,若是你记录了手指最后的位置,须要根据第四个参数offsetInWindow计算偏移量,才能保证下一次的touch事件的计算是正确的。
若是父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,不然为false。
这个函数通常在子view处理scroll前调用。
3、dispatchNestedScroll
向父view汇报滚动状况,包括子view消费的部分和子view没有消费的部分。
若是父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,不然为false。
这个函数通常在子view处理scroll后调用。
4、stopNestedScroll
结束整个流程。
整个对应流程是这样
子view 父view
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll
通常是子view发起调用,父view接受回调。
咱们最须要关注的是dispatchNestedPreScroll中的consumed参数。
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) ;
它是一个int型的数组,长度为2,第一个元素是父view消费的x方向的滚动距离;第二个元素是父view消费的y方向的滚动距离,若是这两个值不为0,则子view须要对滚动的量进行一些修正。正由于有了这个参数,使得咱们处理滚动事件的时候,思路更加清晰,不会像之前同样被一堆的滚动参数搞混。