使用CoordinatorLayout打造各种炫酷的效果

自定义Behavior —— 仿知乎,FloatActionButton隐藏与展示

NestedScrolling 机制深入解析

一步步带你读懂 CoordinatorLayout 源码

自定义 Behavior ——仿新浪微博发现页的实现

简介

NestedScrolling,在 V4 包下面,在 22.10 版本的时候添加进来,支持 5.0 及 5.0 以上的系统。

NestedScrolling,简称嵌套滑动使用它可以实现一些非常绚丽的效果。如知乎的效果,UC 首页的效果,新浪微博发现的效果等。

Google 帮我们封装好了一些相应的空间,比如 RecyclerView 实现了 NestedScrollingChild 接口,CoordinatorLayout 实现了 NestedScrollingParent 接口,NestedScrollingView,SwipeRefreshLayout 实现了 NestedScrollingChild,NestedScrollingParent 接口等。

想比较于传统的事件分发机制,NetstedScroll 机制有什么优点,相信很多人都有这样的疑问?

在传统的事件分发机制 中,一旦某个 View 或者 ViewGroup 消费了事件,就很难将事件交给父 View 进行共同处理。而 NestedScrolling 机制很好地帮助我们解决了这一问题。我们只需要按照规范实现相应的接口即可,子 View 实现 NestedScrollingChild,父 View 实现 NestedScrollingParent ,通过 NestedScrollingChildHelper 或者 NestedScrollingParentHelper 完成交互。

NestedScrolling 机制简述

NestedScrolling 的处理流程

NestedScrolling 机制主要有两个类,

  • NestedScrollingParent

在嵌套滑动中,如果父View 想实现 嵌套滑动,要实现这个 NestedScrollingParent 借口,与 NestedScrollingChild 大概有一一对应的关系。

  • NestedScrollingChild

在嵌套滑动中,如果scrolling child 想实现嵌套滑动,必须实现这个借口

  • NestedScrollingChildHelper

实现 Child 和 Parent 交互的逻辑

  • NestedScrollingParentHelper

实现 Child 和 Parent 交互的逻辑

它的处理流程大概是这样的:

  • scrolling child 在滑动之前,会通过 NestedScrollingChildHelper 查找是否有响应的 scrolling parent,如果有的话,会先询问scrolling parent 是否需要先于scrolling child 滑动,如果需要的话,scrolling parent 进行相应的滑动,并消费一定的距离;
  • 接着scrolling child 进行相应的滑动,并消耗一定的距离值 dx,dy
  • scrolling child 滑动完之后,询问scrolling parent 是否还需要继续进行滑动,需要的话,进行相应的处理。
  • 滑动结束之后,Scrolling child 会停止滑动,并通过 NestedScrollingChildHelper 通知相应的 Scrolling Parent 停止滑动。

为了方便,下文开始,ScrollingChildHelper 用 childHelper 代替,NestedScrollingParentHelper 用parentHelper 代替


NestedScrollingChild 主要方法介绍

目前已知的实现子类有 HorizontalGridView, NestedScrollView, RecyclerView, SwipeRefreshLayout, VerticalGridView

  • boolean startNestedScroll(int axes)

在开始滑动的时候会调用这个方法,axes 代表滑动的方向,ViewCompat.SCROLL_AXIS_HORIZONTAL 代表水平滑动,ViewCompat.SCROLL_AXIS_VERTICAL 代表垂直滑动,

返回值是布尔类型的,根据返回值,我们可以判断是否找到支持嵌套滑动的父View ,返回 true,表示在scrolling parent (需要注意的是这里不一定是直接scrolling parent ,间接scrolling parent 也可会返回 TRUE) 中找到支持嵌套滑动的。反之,则找不到。

  • boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow)

在scrolling child 滑动之前,提供机会让scrolling parent 先于scrolling child滑动。

dx,dy 是输入参数,表示scrolling child 传递给 scrolling parent 水平方向,垂直方向上的偏移量,consumed 是输出参数,consumed[0] 表示父 View 在水平方向上消费的值,,consumed[1 表示父 View 在垂直方向上消费的值。

返回值也是布尔类型的,根据这个值 ,我们可以判断scrolling parent 是都消费了相应距离 。

  • boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow)

在scrolling child 滑动之后,调用这个方法,提供机会给scrolling parent 滑动,dxConsumed,dyConsumed 是输入参数,表示scrolling child 在水平方向,垂直方向消耗的值,dxUnconsumed,dyUnconsumed 也是输入参数,表示scrolling child 在水平方向,垂直方向未消耗的值。

  • boolean dispatchNestedPreFling(float velocityX, float velocityY, boolean consumed)

调用这个方法,在scrolling child 处理 fling 动作之前,提供机会scrolling parent 先于scrolling child 处理 fling 动作。

三个参数都是输入参数,velocityX 表示水平方向的速度,velocityY 表示垂直方向感的速度,consumed 表示scrolling child 是否消费 fling 动作 。

返回值也是布尔类型的,表示scrolling parent 是否有消费了fling 动作或者对 fling 动作做出相应的 处理。true 表示有,false 表示没有。

  • boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed)

在 Scrolling child 处理 fling 动作之后,提供机会给 Scrolling Parent 处理 fling 动作。各个参数的意义这里就不再意义阐述了,跟 dispatchNestedFling 参数的意义是一样的。

  • void stopNestedScroll

当嵌套滑动的时候,会调用这个方法。

在 RecyclerView 中,当 Action_UP 或者 Actioon_cancel 或者 item 消费了 Touch 事件的时候,会调用这个方法。


NestedScrollingParent

Android 中已知的实现子类有 CoordinatorLayout, NestedScrollView, SwipeRefreshLayout。它通常是配合 NestedScrollingChild 进行嵌套滑动的。

  • boolean onStartNestedScroll(View child, View target, int nestedScrollAxes)

在 Scrolling Child 开始滑动的时候会调用这个方法

当 Scrolling Child 调用 onStartNestedScroll 方法的时候,通过 NestedScrollingChildHelper 会回调 Scrolling parent 的 onStartNestedScroll 方法,如果返回 true, Scrolling parent 的 onNestedScrollAccepted(View child, View target, int nestedScrollAxes) 方法会被回调。

target 表示发起滑动事件的 View,Child 是 ViewParent 的直接子View,包含 target,nestedScrollAxes 表示滑动方向。

  • void onNestedScrollAccepted(View child, View target, int nestedScrollAxes)

如果 Scrolling Parent 的onStartNestedScroll 返回 true, Scrolling parent 的 onNestedScrollAccepted(View child, View target, int nestedScrollAxes) 方法会被回调。

  • boolean onNestedPreScroll(View target, int dx, int dy, int[] consumed)

在 Scrolling Child 进行滑动之前,Scrolling Parent 可以先于Scrolling Child 进行相应的处理

如果 Scrolling Child 调用 dispatchNestedPreFling(float velocityX, float velocityY) ,通过 NestedScrollingChildHelper 会回调 Scrolling parent 的 onNestedPreScroll 方法

接下来的几个方法,我们不一一介绍了。与 Scrolling Child 方法几乎是一一对应的。

NetsedScrollingchildHelper 与 NestedScrollingParentHelper

我们知道 RecyclerView 是实现了 NestedScrollingChild 接口,下面我们一起来看一下RecyclerView 是怎样将事件传递给 Scrolling Parent 的。

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild

private NestedScrollingChildHelper getScrollingChildHelper() {if (mScrollingChildHelper == null) {mScrollingChildHelper = new NestedScrollingChildHelper(this);}return mScrollingChildHelper;
}@Override
public void setNestedScrollingEnabled(boolean enabled) {getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}@Override
public boolean isNestedScrollingEnabled() {return getScrollingChildHelper().isNestedScrollingEnabled();
}@Override
public boolean startNestedScroll(int axes) {return getScrollingChildHelper().startNestedScroll(axes);
}@Override
public void stopNestedScroll() {getScrollingChildHelper().stopNestedScroll();
}@Override
public boolean hasNestedScrollingParent() {return getScrollingChildHelper().hasNestedScrollingParent();
}@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,int dyUnconsumed, int[] offsetInWindow) {return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,dxUnconsumed, dyUnconsumed, offsetInWindow);
}@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}

从代码中可以看到,它的很多逻辑都是交给 ChildHelper 去帮助 其完成的,下面我们一起来看一下 ChildHelper 里面的方法。

startNestedScroll 方法

public boolean startNestedScroll(int axes) {if (hasNestedScrollingParent()) {// Already in progressreturn true;}// 判断是否支持嵌套滑动,默认是支持的if (isNestedScrollingEnabled()) {ViewParent p = mView.getParent();View child = mView;// 从直接父 View 找起,看是否支持嵌套滑动while (p != null) {// //回调了父View的onStartNestedScroll方法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()p = p.getParent();}}return false;
}
  • 第一步,判断 P 是否为空,不为空, 从 P (初始值是RecyclerView 的直接父 View) 开始找起,判断其是否支持嵌套滑动,若支持,返回true,
  • 第二步:若 P 不支持嵌套滑动,再将 p 指向 p.getParent(); 循环第一步
  • 第三步:若循环了所有的 P ,都找不到支持嵌套滑动的 View,返回 false。

dispatchNestedScroll 方法

public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {// 有滑动的偏移量if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {int startX = 0;int startY = 0;if (offsetInWindow != null) {mView.getLocationInWindow(offsetInWindow);// 保存刚开始 x 在 window 坐标系的偏移量startX = offsetInWindow[0];// 保存刚开始 y 方向在 window 坐标系的偏移量startY = offsetInWindow[1];}// 调用 mNestedScrollingParent 的 onNestedScroll 方法ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,dyConsumed, dxUnconsumed, dyUnconsumed);// offsetInWindow 不为空if (offsetInWindow != null) {mView.getLocationInWindow(offsetInWindow);// 得到 x 方向在 Window 坐标系的偏移量offsetInWindow[0] -= startX;// 得到 y 方向在 Window 坐标系的偏移量offsetInWindow[1] -= startY;}return true;} else if (offsetInWindow != null) {// No motion, no dispatch. Keep offsetInWindow up to date.offsetInWindow[0] = 0;offsetInWindow[1] = 0;}}return false;
}

简单来说就是根据上一步在 startScrolled 方法中得到的 mNestedScrollingParent,调用 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
dyConsumed, dxUnconsumed,dyUnconsumed);再根据是否有位移,做相应的处理。

看完了上面的两个主要方法,我们可以得出这样的一个结论:当我们调用 Scrolling Child 的 onStartNested 方法的时候,会通过 ChildHelper 去寻找是否有相应的 Scrolling Parent,如果有的话,会 回调相应的方法。同理 dispatchNestedPreScroll,dispatchNestedScroll,dispatchNestedPreFling 也是如此,这里不再一一带大家去看里面是怎样实现的,有兴趣的可以自己去阅读。


startNestedScroll ,dispatchNestedPreScroll 等方法的调用时机

这里我们同样以 RecyclerView 为例讲解:在 OnTouchEvent 方法里面,可以看到会根据不同的 Action 回调不同的方法,这里就不一一阐述了,回调方法的 事件请看代码。

public boolean onTouchEvent(MotionEvent e) {---// 如果 Item 处理了 Touch 事件,直接返回 true ,在在处理if (dispatchOnItemTouch(e)) {cancelTouch();return true;}if (mLayout == null) {return false;}if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}boolean eventAddedToVelocityTracker = false;-------switch (action) {case MotionEvent.ACTION_DOWN: {mScrollPointerId = e.getPointerId(0);mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;if (canScrollHorizontally) {nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;}if (canScrollVertically) {nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;}// 在 Action_Down 的时候 调用 startNestedScrollstartNestedScroll(nestedScrollAxis);} break;----case MotionEvent.ACTION_MOVE: {// 在 Action_move 的时候,回调 dispatchNestedPreScroll 方法if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {// 减去 Scrolling Parent 的消费的值dx -= mScrollConsumed[0];dy -= mScrollConsumed[1];vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);// Updated the nested offsetsmNestedOffsets[0] += mScrollOffset[0];mNestedOffsets[1] += mScrollOffset[1];}----if (mScrollState == SCROLL_STATE_DRAGGING) {mLastTouchX = x - mScrollOffset[0];mLastTouchY = y - mScrollOffset[1];// 在 scrollByInternal 方法里面会回调 onNestedScroll 方法if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,vtev)) {getParent().requestDisallowInterceptTouchEvent(true);}if (mGapWorker != null && (dx != 0 || dy != 0)) {mGapWorker.postFromTraversal(this, dx, dy);}}}break;case MotionEvent.ACTION_UP: {---// 在 fling 方法里面会回调 onNestedPreFling dispatchNestedFling 等方法if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {setScrollState(SCROLL_STATE_IDLE);}// 在手指抬起的时候回调 onStopScroll 方法resetTouch();} break;case MotionEvent.ACTION_CANCEL: {// 在 ACTION_CANCEL 的时候回调 onStopScroll 方法cancelTouch();} break;}if (!eventAddedToVelocityTracker) {mVelocityTracker.addMovement(vtev);}vtev.recycle();return true;
}
private void resetTouch() {if (mVelocityTracker != null) {mVelocityTracker.clear();}stopNestedScroll();releaseGlows();
}private void cancelTouch() {resetTouch();setScrollState(SCROLL_STATE_IDLE);
}

总结

NestedScrollingChild 与 NestedScrollingParent 方法的对应关系

子View 父View 方法描述
startNestedScroll onStartNestedScroll、onNestedScrollAccepted Scrolling Child 开始滑动的时候,通知 Scrolling Parent 要开始滑动了,通常是在 Action_down 动作 的时候调用这个方法
dispatchNestedPreScroll onNestedPreScroll 在 Scrolling Child 要开始滑动的时候,询问 Scrolling Parent 是否先于 Scrolling Child 进行相应的处理,同时是在 Action_move 的时候调用
dispatchNestedScroll onNestedScroll 在 Scrolling Child 滑动后会询问 Scrolling Parent 是否需要继续滑动
dispatchNestedPreFling onNestedPreFling 在 Scrolling Child 开始处理 Fling 动作的时候,询问 Scrolling Parent 是否需要先处理 Fling 动作
dispatchNestedFling onNestedFling 在 Scrolling Child 处理 Fling 动作完毕的时候,询问 Scrolling Parent 是都还需要进行相应的处理
stopNestedScroll onStopNestedScroll 在 Scrolling Child 停止滑动的时候,会调用 Scrolling Parent 的这个方法。通常是在 Action_up 或者 Action_cancel 或者被别的 View 消费 Touch 事件的时候调用的

执行流程

  1. 在 Action_Down 的时候,Scrolling child 会调用 startNestedScroll 方法,通过 childHelper 回调 Scrolling Parent 的 startNestedScroll 方法
  2. 在 Action_move 的时候,Scrolling Child 要开始滑动的时候,会调用dispatchNestedPreScroll 方法,通过 ChildHelper 询问 Scrolling Parent 是否要先于 Child 进行 滑动,若需要的话,会调用 Parent 的 onNestedPreScroll 方法,协同 Child 一起进行滑动
  3. 当 ScrollingChild 滑动完成的时候,会调用 dispatchNestedScroll 方法,通过 ChildHelper 询问 Scrolling Parent 是否需要进行滑动,需要的话,会 调用 Parent 的 onNestedScroll 方法
  4. 在 Action_down,Action_move 的时候,会调用 Scrolling Child 的stopNestedScroll ,通过 ChildHelper 询问 Scrolling parent 的 stopNestedScroll 方法。
  5. 如果需要处理 Fling 动作,我们可以通过 VelocityTrackerCompat 获得相应的速度,并在 Action_up 的时候,调用 dispatchNestedPreFling 方法,通过 ChildHelper 询问 Parent 是否需要先于 child 进行 Fling 动作
  6. 在 Child 处理完 Fling 动作时候,如果 Scrolling Parent 还需要处理 Fling 动作,我们可以调用 dispatchNestedFling 方法,通过 ChildHelper ,调用 Parent 的 onNestedFling 方法

最后的最后,卖一下广告,欢迎大家关注我的微信公众号,扫一扫下方二维码或搜索微信号 stormjun,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。

NestedScrolling 机制深入解析相关推荐

  1. Apache Web服务器访问控制机制全解析

    Apache Web服务器访问控制机制全解析 原文请见: http://netsecurity.51cto.com/art/201102/245666.htm Linux下的Aapche服务器提供了强 ...

  2. 【转】Android事件分发机制完全解析,带你从源码的角度彻底理解(下)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9153761 记得在前面的文章中,我带大家一起从源码的角度分析了Android中Vi ...

  3. NestedScrolling机制

    2019独角兽企业重金招聘Python工程师标准>>> NestedScrolling机制(可以称为嵌套滚动或嵌套滑动)能够让父view和子view在滚动时进行配合,其基本流程如下: ...

  4. Android 系统(59)---Android开发:Handler异步通信机制全面解析(包含Looper、Message Queue)

    Android开发:Handler异步通信机制全面解析(包含Looper.Message Queue) 前言 最近刚好在做关于异步通信的需求,那么,今天我们来讲解下Android开发中的Handler ...

  5. php mvc例子,PHP_ThinkPHP的MVC开发机制实例解析,ThinkPHP是目前国内应用非常广 - phpStudy...

    ThinkPHP的MVC开发机制实例解析 ThinkPHP是目前国内应用非常广泛的一款MVC开发框架.本文就以实例形式解析ThinkPHP的MVC开发机制.相信会给大家一定的启发作用.具体分析如下: ...

  6. Android事件分发机制完全解析,带你从源码的角度彻底理解(上)

    <div id="container">         <div id="header">     <div class=&qu ...

  7. 顺藤摸瓜RocketMQ之刷盘机制debug解析

    文章目录 Rocketmq 刷盘机制 三个文件 indexFile consumeQueue commitlog 异步刷盘 consumerqueue和indexfile文件是什么时候更新的 同步刷盘 ...

  8. AsyncTask机制原理解析

    AsyncTask机制原理解析 Android为我们提供了2种方便的异步处理方案,Handler和AsyncTask,两种方式适合的场景网上一搜就知道了,但是为什么呢?这篇分析将为你揭晓答案.前面分析 ...

  9. Handler机制原理解析(二)prepare,loop,post

    Handler机制原理解析(二)prepare,loop,post 上一篇已经介绍了Handler机制的原理,如果不熟悉可以看Handler机制原理解析(一).这一篇,介绍下Handler周边的知识点 ...

最新文章

  1. 希捷发布CORTX对象存储软件与开源社区,普惠超大规模数据存储
  2. 图解phpstorm常用快捷键
  3. 有一天人人都会变成程序猿
  4. C++子类析构时要调用父类的析构函数吗?
  5. 网络:常见的端口号及分类
  6. Xshell报错“The remote SSH server rejected X11 forwarding request.”
  7. 2185. 统计包含给定前缀的字符串
  8. Java设计模式开篇
  9. ubuntu启动时的初始化信息二
  10. nsis升级包_NSIS制作软件升级安装包 - 卡饭网
  11. Uncaught RangeError: Maximum call stack size exceeded
  12. 刘汝佳Dijkstra模板
  13. win7设置右键+T 快捷键 快速新建文本文档
  14. snmp No Such Instance currently exists at this OID
  15. iOS开发通过微信学习hijack(一)函数劫持
  16. 软考信息系统项目管理师论成本管理范文
  17. xp安全模式下如何修复计算机,xp系统如何进入安全模式|进入安全模式修复系统...
  18. 【企业分析】拉勾、Boss直聘、猎聘
  19. 【工具推荐】常用前端开源静态网站推荐
  20. 如何做好软文推广?软文推广有哪些宣传平台值得推荐

热门文章

  1. html parent 属性,parentNode属性怎么用?
  2. 解决Github访问速度慢以及图片加载慢的问题
  3. Android开发基础:利用 Android Studio开发看美女应用(4)
  4. php smarty语法,Smarty模板引擎视图输出基本语法解析
  5. Python量化策略:经波动率调整的随机震荡指标
  6. 洛谷2046 BZOJ2007 NOI2010 海拔 平面图最小割
  7. fopen php漏洞,在php的fopen中利用漏洞
  8. linux中扩充磁盘空间
  9. abstract抽象类
  10. h3c路由器配置ssh登录方式