浅析:Android 嵌套滑动机制(NestedScrolling)

谷歌在发布安卓 Lollipop版本之后,为了更好的用户体验,Google为Android的滑动机制提供了NestedScrolling特性

NestedScrolling的特性可以体现在哪里呢?

比如你使用了Toolbar,下面一个ScrollView,向上滚动隐藏Toolbar,向下滚动显示Toolbar,这里在逻辑上就是一个NestedScrolling —— 因为你在滚动整个Toolbar在内的View的过程中,又嵌套滚动了里面的ScrollView。

在这之前,我们知道Android对Touch事件的分发是有自己一套机制的。主要是有是三个函数:

dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。

这种分发机制让移动应用安全检测平台-爱内测(ineice.com)发现有一个漏洞,据爱内测的CTO介绍:

如果子view获得处理touch事件机会的时候,父view就再也没有机会去处理这个touch事件了,直到下一次手指再按下。

也就是说,我们在滑动子View的时候,如果子View对这个滑动事件不想要处理的时候,只能抛弃这个touch事件,而不会把这些传给父view去处理。

但是Google新的NestedScrolling机制就很好的解决了这个问题。

我们看看如何实现这个NestedScrolling,首先有几个类(接口)我们需要关注一下

NestedScrollingChild

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交互,它整个工作流是这样的:

一、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

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需要对滚动的量进行一些修正。正因为有了这个参数,使得我们处理滚动事件的时候,思路更加清晰,不会像以前一样被一堆的滚动参数搞混。

时间: 2024-10-14 20:01:05

浅析:Android 嵌套滑动机制(NestedScrolling)的相关文章

详解:Android嵌套滑动机制 (NestedScrolling)

从 Android 5.0 Lollipop 开始提供一套 API 来支持嵌入的滑动效果.同样在最新的 Support V4 包中也提供了前向的兼容.有了嵌入滑动机制,就能实现很多很复杂的滑动效果.在 Android Design Support 库中非常总要的 CoordinatorLayout 组件就是使用了这套机制,实现了 Toolbar 的收起和展开功能,如下图所示: NestedScrolling提供了一套父 View 和子 View 滑动交互机制.要完成这样的交互,父 View 需要

使用Android SwipeRefreshLayout了解Android的嵌套滑动机制

SwipeRefreshLayout 是在Android Support Library, revision 19.1.0添加到support v4库中的一个下拉刷新控件,关于android的下拉刷新框架现在有好多,曾经用过XListView,现在工作中基本上无需用到下拉刷新的功能.废话不多说了,这里来记录一下android自带的刷新控件SwipeRefreshLayout的使用,借此顺便来熟悉一下android 在Lollipop版本推出的嵌套滑动机制(NestedScrolling). 首先

Android嵌套滑动控件的冲突解决和ViewPager适配当前子控件高度不留空白的办法

最近项目有一个需求,需要多层可滑动控件的嵌套展示,demo效果如下,demo的下载地址在最后 咋一看好像挺简单啊,不就是一个ScrollView + ViewPager + ListView吗,我开始也这样觉得,也用的这种方式实现,结果始终和效果不对劲.这里总结几点问题: 两个或两个以上的滑动控件嵌套时,如果layout_height采用的是wrap_content会造成内部滑动控件的高度不能正确的计算,会导致内部滑动控件的高度始终为0,除非你用定值设置,比如300dp. 两个相同滑动方向的滑动

Android嵌套滑动不流畅记录随笔

---恢复内容开始--- 今天第一次用到ScrollView嵌套RecyclerView来做页面. 刚开始效果开心得很,非常Very漂亮噢! 纳尼!!!沃特Fuck!出事儿,出事儿! 滑动为何如此不流畅,随后经过研究加上了一个关键属性,如下: recyclerview.setNestedScrollingEnabled(false); 加上之后,朕的江山,又回来了! 此随笔用做记录,以便以后之需,希望也可以帮到大家,End~. ---恢复内容结束--- 原文地址:https://www.cnbl

Android NestedScrolling机制解析 带你玩爆嵌套滑动

源码地址: CSDN:  http://download.csdn.net/detail/xiaole0313/9612250 GitHub:https://github.com/xiaole0310/Android-StickyNavLayout 一.概述 Android在support.v4包中为大家提供了两个非常神奇的类: NestedScrollingParent NestedScrollingChild 如果你从未听说过这两个类,没关系,听我慢慢介绍,你就明白这两个类可以用来干嘛了.相

Android的消息处理机制——Looper,Handler和Message浅析

题外话: 说来有些惭愧,对于这三者的初步认识居然是在背面试题的时候.那个时候自己接触Android的时间还不长,学习的书籍也就是比较适合入门的<疯狂Android讲义>,当然在学到Handler这一部分的时候,书中也是有提到一些简单示例,后来在工作中需要用到这个MessageQueue的时候才开始真正琢磨了一下这三者的联系.如果想要对这三者好好理解一番,个人还是比较推荐<深入理解Android卷Ⅰ>.以下对这三者之间的恩怨纠葛的介绍和分析也是参考这本书的相关章节,算是一篇读书笔记吧

浅析Android Handle机制

一.Handle的用例: 1.创建handle实例 new handle(); 2.发送信息载体(Message) sendMessage(msg); 3.处理消息 handleMessage(Message msg){}; 二.原理浅析 结合以上的handle调用三部曲,我们将顺藤摸瓜理清Handle.Looper.Message.MessageQueue的逻辑与关系. 1.new Handle():这个操作将生成一个Handle实例,handle实例有三个属性mLooper.mQueue.m

Android View工作机制浅析(ppt)

Android View工作机制浅析(ppt)

Android事件分发机制浅析(3)

本文来自网易云社区 作者:孙有军 我们只看最重要的部分 1: 事件为ACTION_DOWN时,执行了cancelAndClearTouchTargets函数,该函数主要清除上一次点击传递的路径,之后执行了resetTouchState,重置了touch状态,其中执行了 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;就是拦截状态为false,这个与requestDisallowInterceptTouchEvent函数相关. 2: 获取intercepted的值,