NestedScroll机制


所谓嵌套滚动,是针对View与ViewGroup来说的,即二者联动。
当我们手指在一个View上滑动一定delta距离时,通过scrollTo让View实现delta距离的滚动,触屏事件完全消耗在View上面,父View无法消费滑动事件。为了让View与父View同时对某一滑屏距离做出滑动反应,View和ViewGroup的交互接口ViewParent包含NestedScroll相关方法,于是就有了嵌套滚动这个东西。

View中NestScroll方法如图所示。
NestScroll_View.png

ViewGroup实现ViewParent接口中NestScroll方法如图所示。
NestScroll_ViewGroup.png
嵌套滑动的核心本质有两种情况。

一种是父View主动型,子View在消费事件滑动的距离时,先问问父View,父View主动决定自己消耗掉多少距离,然后给子View留多少距离。
另一种是子View主动型,子View先消耗,然后将消耗与未消耗的距离一起告诉父View。

注意一点,嵌套滚动父视图的方法都是onXxx开头的,有的视图既可以在嵌套滚动中作为子视图,也能作为父视图。


查找可接受嵌套滚动的父视图

View开始准备NestScroll嵌套滚动时,触发View#startNestedScroll方法。
View#startNestedScroll方法。

public boolean startNestedScroll(int axes) {
    //已经存在mNestedScrollingParent的节点,直接返回
    if (hasNestedScrollingParent()) {
        return true;
    }
    if (isNestedScrollingEnabled()) {
        ViewParent p = getParent();
        View child = this;
        while (p != null) {
            try {
                if (p.onStartNestedScroll(child, this, axes)) {
                    mNestedScrollingParent = p;
                    p.onNestedScrollAccepted(child, this, axes);
                    return true;
                }
            } catch (AbstractMethodError e) {
            }
            if (p instanceof View) {
                child = (View) p;
            }
            p = p.getParent();
        }
    }
    return false;
}
  • hasNestedScrollingParent方法,View内部的mNestedScrollingParent,如果已经初始化过,直接返回true。否则,需要向上找到一个配合嵌套滚动的父视图。
  • View支持嵌套滚动时,isNestedScrollingEnabled方法是true,如果一个View支持嵌套滚动,提前通过setNestedScrollingEnabled方法设置mPrivateFlags3标志位。
  • 在树视图结构中,向上一层层查找ViewParent父节点,调用ViewParent#onStartNestedScroll方法,父视图支持嵌套滚动的条件是onStartNestedScroll方法返回true,ViewGroup#onStartNestedScroll默认返回值false。因此,支持NestScroll的父容器需重写onStartNestedScroll方法。
  • 如果没有父视图的onStartNestedScroll方法是true,说明均不支持嵌套滚动。

ViewGroup#onStartNestedScroll方法

@Override
public boolean onStartNestedScroll(View child, View target,  
                        int nestedScrollAxes) {
    return false;
}
  • 重写ViewGroup#onStartNestedScroll方法,父容器接受嵌套滚动,将View的mNestedScrollingParent设为该父容器。
  • ViewGroup#onNestedScrollAccepted方法设置axes滚动轴。

父视图的onStartNestedScroll方法需要重写,返回true,才会支持配合子视图嵌套滚动,同时,赋值子视图内部mNestedScrollingParent。
通过startNestedScroll验证同步View与父View支持NestedScroll后,下面开启滚动。


子视图dispatchNestedPreScroll方法

View#dispatchNestedPreScroll,把自己的滚动机会先让给父View
可以在子视图触摸事件中调用,该方法在基类View。

View#dispatchNestedPreScroll方法。

public boolean dispatchNestedPreScroll(int dx, int dy,
                    int[] consumed,  int[] offsetInWindow) {
    //执行条件是本视图是支持嵌套滚动且内部支持的父视图不空。
    if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
        if (dx != 0 || dy != 0) {
            int startX = 0;
            int startY = 0;
            ...
            //先初始化为0,
            consumed[0] = 0;
            consumed[1] = 0;
            mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
            ...
            return consumed[0] != 0 || consumed[1] != 0;
        } else if (offsetInWindow != null) {
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
}

父View触发onNestedPreScroll方法时,通过父View的scrollBy方法移动父View的内部视图,将移动的(即消耗掉)的距离存储在consumed中,通过consumed数组通知子View,我已经消耗掉你应该移动的距离了,你自己就别动了,跟我一起动吧,这时处理的是子View的onTouch事件。

父View先消耗,子View紧跟着父View移动,未消耗掉的子View继续消耗,让子View与父View联动。
父View重写onNestedPreScroll方法。ViewGroup#onNestedPreScroll方法默认什么都不做,最近发现23版本的ViewGroup在该方法什么都没做,而27版本的默认会触发dispatchNestedPreScroll方法,意思就是,如果父视图也是可以嵌套滚动,去找他自己的父视图去消费哈,如果上层没有支持的父视图,也什么都不做了。


子视图dispatchNestedScroll方法

View#dispatchNestedScroll,把自己的滚动机会先让给自己。

View#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;
            ...
            mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
                        dxUnconsumed, dyUnconsumed);
            ...
            return true;
        } else if (offsetInWindow != null) {
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
}

父View触发onNestedScroll方法时,子View主动将消费的距离与未消费的距离通知父View。

父View重写onNestedScroll方法。ViewGroup#onNestedScroll方法默认什么都不做。
当父View收到子View未能消费的距离后,在父View的坐标下完成剩余偏移。这种场景是子View主动先消费,剩下的交给父View。

总结一下

注意ViewGroup的onNestedScroll方法和onNestedPreScroll方法,他们都是去消费子视图的移动距离。
dispatchNestedPreScroll和dispatchNestedScroll的区别,前者把机会先让给父视图,后者把机会先留给自己。


任重而道远

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容