详解Android中的NestedScrolling机制带你玩转嵌套滑动

详解Android中的NestedScrolling机制带你玩转嵌套滑动,第1张

概述一、概述Android在support.v4包中为大家提供了两个非常神奇的类:NestedScrollingParent

一、概述

AndroID在support.v4包中为大家提供了两个非常神奇的类:

nestedScrollingParent nestedScrollingChild

如果你从未听说过这两个类,没关系,听我慢慢介绍,你就明白这两个类可以用来干嘛了。相信大家都见识过或者使用过CoordinatorLayout,通过这个类可以非常便利的帮助我们完成一些炫丽的效果,例如下面这样的:

这样的效果就非常适合使用nestedScrolling机制去完成,并且CoordinatorLayout背后其实也是利用着这套机制,So,我相信你已经明白这套机制可以用来干嘛了。

但是,我相信你还有个问题

这个机制相比传统的自定义viewGroup事件分发处理有什么优越的地方吗?

恩,我们简单分析下:

按照上图:

假设我们按照传统的事件分发去理解,首先我们滑动的是下面的内容区域,而移动却是外部的VIEwGroup在移动,所以按照传统的方式,肯定是外部的Parent拦截了内部的Child的事件;但是,上述效果图,当Parent滑动到一定程度时,Child又开始滑动了,中间整个过程是没有间断的。从正常的事件分发(不手动调用分发事件,不手动去发出事件)角度去做是不可能的,因为当Parent拦截之后,是没有办法再把事件交给Child的,事件分发,对于拦截,相当于一锤子买卖,只要拦截了,当前手势接下来的事件都会交给Parent(拦截者)来处理。

但是nestedScrolling机制来处理这个事情就很好办,所以对这个机制进行深入学习,一来有助于我们编写嵌套滑动时一些特殊的效果;二来是我为了对CoordinatorLayout做分析的铺垫~~~

ps:具体在哪个v4版本中添加的,就不去深究了,如果你的v4中没有上述两个类,升级下你的v4版本。nestedScrolling机制这个词,个人称呼,不清楚官方有没有这么叫,勿深究。

二、预期效果

当然讲解这两个类,肯定要有案例的支撑,不然太过于空洞了。好在,我这里有个非常好的案例可以来描述:

很久以前,我写过这样一篇文章:

Android通过自定义控件实现360软件详情页效果

完全按照传统的方式去编写的,而且为了连续滑动,做了一些非常特殊处理,比如手动去分发DOWN事件类的,有兴趣可以阅读下。

效果图是这样的:

今天我们就利用这个效果,作为nestedSroll机制的案例,最后我们还会简单分析一下源码,其实源码还是比较简单的~~

ps:CoordinatorLayout可以很方便实现该效果,后续的文章也会对CoordinateLayout做一些分析。

三、实现

上述效果图,分为3部分:顶部布局;中间的VIEwPager指示器;以及底部的RecyclerVIEw;

RecyclerVIEw其实就是nestedSrollingChild的实现类,所以本例主要的角色是去实现nestedScrollingParent.

(1)布局文件

首先预览下布局文件,脑子里面有个大致的布局:

<com.zhy.vIEw.StickyNavLayout xmlns:tools="http://schemas.androID.com/tools" xmlns:androID="http://schemas.androID.com/apk/res/androID" androID:layout_wIDth="match_parent" androID:layout_height="match_parent" androID:orIEntation="vertical" > <relativeLayout androID:ID="@ID/ID_stickynavlayout_topvIEw" androID:layout_wIDth="match_parent" androID:layout_height="wrap_content" androID:background="#4400ff00" > <TextVIEw  androID:layout_wIDth="match_parent"  androID:layout_height="256dp"  androID:gravity="center"  androID:text="软件介绍"  androID:textSize="30sp"  androID:textStyle="bold" /> </relativeLayout> <com.zhy.vIEw.SimpleVIEwPagerIndicator androID:ID="@ID/ID_stickynavlayout_indicator" androID:layout_wIDth="match_parent" androID:layout_height="50dp" androID:background="#ffffffff" > </com.zhy.vIEw.SimpleVIEwPagerIndicator> <androID.support.v4.vIEw.VIEwPager androID:ID="@ID/ID_stickynavlayout_vIEwpager" androID:layout_wIDth="match_parent" androID:layout_height="match_parent"  > </androID.support.v4.vIEw.VIEwPager></com.zhy.vIEw.StickyNavLayout>

StickyNavLayout是直接继承自linearLayout的,并且设置的是orIEntation="vertical",所以直观的就是控件按顺序纵向排列,至于测量需要做一些特殊的处理,因为不是本文的重点,可以自己查看源码,或者上面提到的文章。

(2) 实现nestedScrollingParent

nestedScrollingParent是一个接口,实现它需要实现如下方法:

public boolean onStartnestedScroll(VIEw child,VIEw target,int nestedScrollAxes);public voID onnestedScrollAccepted(VIEw child,int nestedScrollAxes);public voID onnestedScroll(VIEw target,int dxconsumed,int dyConsumed,int dxUnconsumed,int dyUnconsumed);public voID onnestedPreScroll(VIEw target,int dx,int dy,int[] consumed);public boolean onnestedFling(VIEw target,float veLocityX,float veLocityY,boolean consumed);public boolean onnestedPreFling(VIEw target,float veLocityY);public int getnestedScrollAxes();

在写具体的实现前,先对需要用到的上述方法做一下简单的介绍:

onStartnestedScroll该方法,一定要按照自己的需求返回true,该方法决定了当前控件是否能接收到其内部VIEw(非并非是直接子VIEw)滑动时的参数;假设你只涉及到纵向滑动,这里可以根据nestedScrollAxes这个参数,进行纵向判断。 onnestedPreScroll该方法的会传入内部VIEw移动的dx,dy,如果你需要消耗一定的dx,dy,就通过最后一个参数consumed进行指定,例如我要消耗一半的dy,就可以写consumed[1]=dy/2 onnestedFling你可以捕获对内部VIEw的fling事件,如果return true则表示拦截掉内部VIEw的事件。

主要关注的就是这三个方法~

这里内部VIEw表示不一定非要是直接子VIEw,只要是内部VIEw即可。

下面看一下我们具体的实现:

public class StickyNavLayout extends linearLayout implements nestedScrollingParent{ @OverrIDe public boolean onStartnestedScroll(VIEw child,int nestedScrollAxes) { return (nestedScrollAxes & VIEwCompat.SCRolL_AXIS_VERTICAL) != 0; } @OverrIDe public voID onnestedPreScroll(VIEw target,int[] consumed) { boolean hIDdentop = dy > 0 && getScrollY() < mtopVIEwHeight; boolean showtop = dy < 0 && getScrollY() > 0 && !VIEwCompat.canScrollVertically(target,-1); if (hIDdentop || showtop) {  scrollBy(0,dy);  consumed[1] = dy; } } @OverrIDe public boolean onnestedPreFling(VIEw target,float veLocityY) { if (getScrollY() >= mtopVIEwHeight) return false; fling((int) veLocityY); return true; }}
onStartnestedScroll中,我们判断了如果是纵向返回true,这个一般是需要内部的VIEw去传入的,你要是不确定,或者担心内部VIEw编写的不规范,你可以直接return true; onnestedPreScroll中,我们判断,如果是上滑且顶部控件未完全隐藏,则消耗掉dy,即consumed[1]=dy;如果是下滑且内部VIEw已经无法继续下拉,则消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去执行scrollBy,实际上就是我们的StickNavLayout滑动。 此外,这里还处理了fling,通过onnestedPreFling方法,这个可以根据自己需求定了,当顶部控件显示时,fling可以让顶部控件隐藏或者显示。

以上代码就能实现下面的效果:

对于fling方法,我们利用了OverScroll的fling的方法,对于边界检测,是重写了scrollTo方法:

public voID fling(int veLocityY){ mScroller.fling(0,getScrollY(),veLocityY,mtopVIEwHeight); invalIDate();}@OverrIDepublic voID scrollTo(int x,int y){ if (y < 0) {  y = 0; } if (y > mtopVIEwHeight) {  y = mtopVIEwHeight; } if (y != getScrollY()) {  super.scrollTo(x,y); }}

详细的解释可以看上面提到的文章,这里就不重复了。

到这里呢,可以看到nestedScrolling机制说白了非常简单:

就是nestedScrollingParent内部的VIEw,在滑动到时候,会首先将dx、dy传入给nestedScrollingParent,nestedScrollingParent可以决定是否对其进行消耗,一般会根据需求消耗部分或者全部(不过这里并没有实际的约束,你可以随便写消耗多少,可能会对内部VIEw造成一定的影响)。

用白话和原本的事件分发机制作对比就是这样的(针对正常流程下一次手势):

事件分发是这样的:子VIEw首先得到事件处理权,处理过程中,父VIEw可以对其拦截,但是拦截了以后就无法再还给子VIEw(本次手势内)。 nestedScrolling机制是这样的:内部VIEw在滚动的时候,首先将dx,dy交给nestedScrollingParent,nestedScrollingParent可对其进行部分消耗,剩余的部分还给内部VIEw。

具体的源码会比本博文复杂,因为涉及到触摸非内部VIEw区域的一些交互,非本博文重点,可以参考源码。

四、原理

原理其实就是看内部VIEw什么时候回调nestedScrollingParent各种方法的,直接定位到内部VIEw的ontouchEvent:

@OverrIDepublic boolean ontouchEvent(MotionEvent e) { switch (action) {  case MotionEvent.ACTION_DOWN: {   int nestedScrollAxis = VIEwCompat.SCRolL_AXIS_NONE;   if (canScrollHorizontally) {    nestedScrollAxis |= VIEwCompat.SCRolL_AXIS_HORIZONTAL;   }   if (canScrollVertically) {    nestedScrollAxis |= VIEwCompat.SCRolL_AXIS_VERTICAL;   }   startnestedScroll(nestedScrollAxis);  } break;  case MotionEvent.ACTION_MOVE: {   if (dispatchnestedPreScroll(dx,dy,mScrollConsumed,mScrollOffset)) {    dx -= mScrollConsumed[0];    dy -= mScrollConsumed[1];    vtev.offsetLocation(mScrollOffset[0],mScrollOffset[1]);   }  } break;  case MotionEvent.ACTION_UP: {   fling((int) xvel,(int) yvel);   resettouch();  } break;  case MotionEvent.ACTION_CANCEL: {   canceltouch();  } break; } return true;}

可以看到:

ACTION_DOWN调用了startnestedScroll;ACTION_MOVE中调用了dispatchnestedPreScroll;ACTION_UP可能会触发fling以调用resettouch。

startnestedScroll内部实际上:

#nestedScrollingChildHelperpublic 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,axes);    return true;   }   if (p instanceof VIEw) {    child = (VIEw) p;   }   p = p.getParent();  } } return false;}

去寻找nestedScrollingParent,然后回调onStartnestedScroll和onnestedScrollAccepted。

dispatchnestedPreScroll中会回调onnestedPreScroll方法,内部的scrollByInternal中还会回调onnestedScroll方法。

fling中会回调onnestedPreFling和onnestedFling方法。

resettouch中则会回调onStopnestedScroll。

代码其实没什么贴的,大家直接找到ontouchEvent一眼就能看到,调用的方法名都是dispatchnestedXXX方法,实际内部都是通过nestedScrollingChildHelper实现的。

所以如果你需要实现和nestedScrollingParent协作的内部VIEw,记得实现nestedScrollingChild,然后内部借助nestedScrollingChildHelper这个辅助类,核心的方法都封装好了,你只需要在恰当的实际去传入参数调用方法即可。

ok,这样的一个机制一定要去试试,很多滑动相关的效果都可以借此实现;

源码地址:

github地址:https://github.com/hongyangAndroid/Android-StickyNavLayout

本地下载:http://xiazai.jb51.net/201705/yuanma/Android-StickyNavLayout(jb51.net).rar

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程小技巧的支持。

总结

以上是内存溢出为你收集整理的详解Android中的NestedScrolling机制带你玩转嵌套滑动全部内容,希望文章能够帮你解决详解Android中的NestedScrolling机制带你玩转嵌套滑动所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1146265.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存