当前位置: 首页 > 科技观察

Android嵌套滑动机制NestedScrolling

时间:2023-03-13 12:18:10 科技观察

这篇文章本来是打算写在简书里的,但是因为页面不能同时支持富文本和markdown,看到爽快又好看的我决定放弃简书双子篇的效果,看文章会很无聊,如果没有审美效果,那你看的时候还不用睡觉吗?网络给了我们这么多的选择,所以我一定会选择体验最好的。具体效果可以对比一下:说到Gemini,这两天也是因为了解了NestedScrolling才接触到NestedScrolling。我快速浏览了一下资料和文章的浏览量。称赞!嵌套滚动。之前了解NestedScrolling的时候看了一些博客,包括Gemini的segmentfault。我看的时候并没有当真。最后发现这篇博客是对NestedScrolling介绍最清楚的。作为惩罚值得崇拜。本来可以cv的博客手动过来了,顺便补充了一些额外的理解。再次感谢GeminiAndroid在Lillipop版本发布后,为了更好的用户体验,Google为Android的滑动机制提供了NestedScrolling机制。NestedScrolling的特性体现在哪里呢?比如你用一个Toolbar,下面一个ScrollView,向上滚动隐藏Toolbar,向下滚动显示Toolbar,这里逻辑上就是一个NestedScrolling——因为你在滚动包括整个Toolbar的View,在这个过程中,ScrollView里面是嵌套和滚动的。如图:在这之前,我们知道Android对于Touch事件的分发有自己的一套机制。主要有三个函数:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。这种分配机制有一个漏洞:如果子视图获得了处理触摸事件的机会,那么父视图将永远没有机会处理触摸事件,直到下一次手指触发。也就是说,当我们滑动子视图时,如果子视图不需要处理滑动事件,那么我们只能丢弃触摸事件,而不是将其传递给父视图进行处理。但是Google新的NestedScrolling机制很好的解决了这个问题。NestedScrolling主要有四个类需要注意:NestedScrollingChildNestedScrollingChildHelperNestedScrollingParentNestedScrollingParentHelper以上四个类在support-v4包中提供,Lollipop中的一些View默认实现了NestedScrollingChild或NestedScrollingParent。v4包中的NestedScrollView同时实现了NestedScrollingChild和NestedScrollingParent。一般实现NestedScrollingChild就可以了,父View可以实现support-design提供的NestedScrollingParent的CoordinatorLayout。@OverridepublicvoidsetNestedScrollingEnabled(booleanenabled){super.setNestedScrollingEnabled(enabled);mChildHelper.setNestedScrollingEnabled(enabled);}@OverridepublicbooleanisNestedScrollingEnabled(){returnmChildHelper.isNestedScrollingEnabled();}@OverridepublicbooleanstartNestedScroll(intaxes){returnmChildHelper.startNestedScroll(axes);}@OverridepublicvoidstopNestedScroll(){mChildHelper.stopNestedScroll();}@OverridepublicbooleanhasNestedScrollingParent(){returnmChildHelper.hasNestedScrollingParent();}@OverridepublicbooleandispatchNestedScroll(intdxConsumed,intdyConsumed,intdxUnconsumed,intdyUnconsumed,int[]offsetInWindow){returnmChildHelper.dispatchNestedScroll(dxConsumed,dyConsumed,dxUnconsumed,dyUnconsumed,offsetInWindow);}@OverridepublicbooleandispatchNestedPreScroll(intdx,intdy,int[]consumed,int[]offsetInWindow){returnmChildHelper.dispatchNestedPreScroll(dx,dy,consumed,offsetInWindow);}@OverridepublicbooleandispatchNestedFling(floatvelocityX),floatvelocityY,booleanconsumed){returnmChildHelper.dispatchNestedFling(velocityX,velocityY,consumed);}@OverridepublicbooleanddispatchNestedPreFling(floatvelocityX,floatvelocityY){returnmChildHelper.dispatchNestedPreFling(velocityX,velocityY);以上接口都是在业务逻辑中自己调用的。NestedScrollingChildHelper是如何实现的?先看startNestedScroll方法/***Startanewnestedscrollforthisview.**

Thisisadelegatemethod.Callitfromyour{@linkandroid.view.ViewView}subclass*method/{@linkandroid.view.ViewView}subclass*method/{@linkandroid.view.ViewView}subclass*method/{@linkandroid.view.ViewView}subclass*method/{@linkandroid.view.ViewView}linkNestedScrollingChild}interfacemethodwiththesamesignaturetoimplement*thestandardpolicy.

**@paramaxesSupportednestedscrollaxes.*见{@linkNestedScrollingChild#startNestedScroll(int)}.*@returntrueifacooperatingparentviewwasfoundandnestedscrollingstartedsuccessfully*/publicbooleanstartNestedScroll(intaxes){if(hasNestedScrollingParent()){//Alreadyinprogressreturntrue;}if(isNestedScrollingEnabled()){ViewParentp=mView.getParent();Viewchild=mView;while(p!=null)){if(ViewParentCompat.onStartNestedScroll(p,child,mView,axes)){mNestedScrollingParent=p;ViewParentCompat.onNestedScrollAccepted(p,child,mView,axes);returntrue;}if(pinstanceofView){child=(View)p;}p=p.getParent();}}returnfalse;}可以看到这里是帮你实现与NestedScrollingParent交互的一些方法。ViewParentCompat是一个与父视图交互的兼容类。判断API版本,如果在Lollipop上,调用View自带的方法。否则,如果判断NestedScrollingParent被实现,则调用实现该接口的方法。子View与父View的交互流程如下:1.startNestedScroll首先,子View需要启动整个流程(滑动屏幕触发触摸事件),找到并通知通过NestedScrollingChildHelper实现NestedScrollingParent的父View。2.dispatchNestedPreScroll在子View的onIterceptTouchEvent和onTouch中(一般在MotionEvent.ACTION_MOVE事件中),调用change方法通知父View滑动距离,该方法第三、四参数返回消耗的滚动长度通过父View和子View的窗口偏移量,如果滚动还没有被消耗,子View会处理剩余的距离。由于窗口移动,如果记录了手指***的位置,则需要根据第四个参数offsetInWindow计算偏移量,以保证下一次触摸事件的计算是正确的。如果父View接受了scroll参数并部分消费,则函数返回true,否则返回false。这个函数一般在子View处理Scroll之前调用。3.dispatchNestedScroll向父View报告滚动状态,包括子View的已消费和未消费的值。如果父View接受滚动参数,则该函数在部分被消费时返回true,否则返回false。这个函数一般在子View处理Scroll之后调用。4、stopNestedScroll结束整个嵌套滑动过程。流程中的NestedScrollingChild和NestedScrollingParent对应如下:dispatchNestedPreScroll中需要注意consumed参数:publicbooleandispatchNestedPreScroll(intdx,intdy,int[]consumed,int[]offsetInWindow);该参数是一个int类型的数组,长度为2,第一个元素为父View消耗的x轴方向的滚动距离,第二个元素为父View消耗的y轴方向的滚动距离父视图。如果两个值都不为0,说明父View已经消耗了滚动距离,需要修正子View的滚动距离。因为有了这个参数,在处理滚动事件的时候思路更加清晰,不会像以前那样被一堆滚动参数搞糊涂了。一个我理解的NestedScrolling的简要流程图(不包括Fling事件和返回值的逻辑):