NestedScrolling解析

1.什么是NestedScrolling?

NestedScrolling(嵌套滑动机制)是Google在Android 5.0版本之后提供的一种新的滑动特性, 是一种嵌套view之间嵌套滑动的互动机制, 更方便于实现复杂的嵌套滑动效果。

2.为什么会有NestedScrolling?

view的touch事件分发机制是自上而下的, 主要是通过这几个函数来处理:dispatchTouchEvent一一传递,onInterceptTouchEvent一一拦截、onTouchEvent一一消费
这种机制有个痛点:

touch事件 要么被 父View 处理,要么被 子View 处理,很难在两者之间进行交互处理。

但Google提供的NestedScrolling机制能够很好地解决这个问题。

3.NestedScrolling的实现分析

NestedScrolling实现主要依赖以下四个类:
** NestedScrollingChild **
** NestedScrollingParent **
** NestedScrollingChildHelper**
** NestedScrollingParentHelper**
NestedScrollingChild 和 NestedScrollingParent 是接口类, NestedScrollingParent 和 NestedScrollingParentHelper则是嵌套父view和子view嵌套滑动交互的辅助类, 主要是实现了嵌套父view与子view之间的交互。所以想要实现NestedScrolling,
嵌套子view需实现NestedScrollingChild 接口, 将相关事件委托给NestedScrollingChildHelper处理
嵌套父view需实现NestedScrollingParent 接口, 将滑动事件委托给NestedScrollingParentHelper处理。
NestedScrollingChild

    //设置是否支持嵌套滑动
    @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();
    }

    //父View是否支持嵌套滑动
    @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);
    }

NestedScrollingParent

   
    //是否允许嵌套滑动
    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(child, target, nestedScrollAxes);
    }

    //onStartNestedScroll()方法返回true会调用该函数
    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
    }

    //嵌套滑动停止
    @Override
    public void onStopNestedScroll(View target) {
        super.onStopNestedScroll(target);
    }

    //子View的嵌套滑动事件
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

   //子View嵌套滑动前的准备动作
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(target, dx, dy, consumed);
    }

    //嵌套滑动子View的滑行情况
    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return super.onNestedFling(target, velocityX, velocityY, consumed);
    }

    //嵌套滑动子View滑行前的准备动作
    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        return super.onNestedPreFling(target, velocityX, velocityY);
    }

    //嵌套滑动方向
    @Override
    public int getNestedScrollAxes() {
        return super.getNestedScrollAxes();
    }

4.NestedScrolling工作流程

在NestedScrolling(嵌套滑动机制)中, 嵌套子View是事件发起者, 嵌套父View则对事件进行响应处理和结果回调。工作流程如下:

4.1 startNestedScroll

子View调用startNestedScroll方法开启嵌套滑动, 寻找支持NestedScrolling的父View, 通知父View开始嵌套滑动,这时父View的onStartNestedScroll和onNestedScrollAccepted会被调用。
具体实现我们看一下NestedScrollingChildHelper里面的方法:

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;
 }

如果父View的onStartNestedScroll返回true,表示接受此次的嵌套滑动, 会继续执行onNestedScrollAccepted方法;
如果返回false则表示不想处理此次的滑动, 那么子View就会继续向上寻找支持滑动的父View。

4.2 dispatchNestedPreScroll

滑动开始前, 子View会调用dispatchNestedPreScroll方法回调父View的onNestedPreScroll方法, 询问父View是否需要进行滑动处理, 如果父View进行了滑动处理, 会将消费的距离信息通过consumed回传给子View, 子View根据父View的消费情况来决定自己的滑动处理。 如果父View不需要滑动处理,则子View自己进行滑动处理。

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;
             if (offsetInWindow != null) {
                 mView.getLocationInWindow(offsetInWindow);
                 startX = offsetInWindow[0];
                 startY = offsetInWindow[1];
             }

             if (consumed == null) {
                 if (mTempNestedScrollConsumed == null) {
                     mTempNestedScrollConsumed = new int[2];
                 }
                 consumed = mTempNestedScrollConsumed;
             }
             consumed[0] = 0;
             consumed[1] = 0;
             ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);

             if (offsetInWindow != null) {
                 mView.getLocationInWindow(offsetInWindow);
                 offsetInWindow[0] -= startX;
                 offsetInWindow[1] -= startY;
             }
             return consumed[0] != 0 || consumed[1] != 0;
         } else if (offsetInWindow != null) {
             offsetInWindow[0] = 0;
             offsetInWindow[1] = 0;
         }
     }
     return false;
 }

4.3 dispatchNestedScroll

子view滑动后,可调用dispatchNestedScroll将自己的滑动情况告诉父View, 父View的onNestedScroll会被回调,如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false
ps: dispatchNestedFling 与dispatchNestedScroll类似

4.4 stopNestedScroll

子View调用stopNestedScroll结束整个滑动流程, 父view的onStopNestedScroll方法被回调结束滑动

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

推荐阅读更多精彩内容