NestedScrolling 0

2017-04-18  本文已影响0人  比目鱼26

Why

Android事件分发机制一般情况下能满足大部分情况下View事件处理,其中的关键点就是事件拦截和事件上传,具体的详情这里不做展开。假设现在有这样一种场景,ViewGroup有且仅有一个Child View,�
要求是Child消费down事件、ViewGroup消费move事件,这种情况就不太好处理,原因是Child消费down事件后,后续的move、up和cancel的onTouchEvent返回false, 事件都会发送到Child, ViewGroup不会有机会处理。可以看出down事件是否消费很关键,根据默认的事件分发机制似乎不能完成这里的要求,好在google提供了design(Android L版本提出), design库其中的一个重要特性就是嵌套滑动机制。�

What

嵌套滑动可以简单的理解成父(ViewGroup)和子(View或者ViewGroup)共同处理move事件,流程是这样的:�子View先将滑动事件交给父View处理,�父View根据情况消耗全部或者部分move偏移量,子View再消耗剩余的move偏移量,最后子View再通知父View scroll消耗的情况,父View根据这个需求再进行滑动,整个过程的顺序如下:

0,父View先处理scroll
1,子View再处理scroll
2,父View再处理scroll
3,子View再处理scroll

元素

主要接口有2个,分别是 NestedScrollingParent
NestedScrollingChild
,父 View 需要实现 NestedScrollingParent接口,而子 View 需要实现 NestedScrollingChild接口。

NestedScrollingChild接口的源码如下:

public interface NestedScrollingChild {
    //设置是否开启嵌套滑动功能
    public void setNestedScrollingEnabled(boolean enabled);
    public boolean isNestedScrollingEnabled();
    //开始一个新的嵌套滑动,参数指定横向或者纵向
    public boolean startNestedScroll(int axes);
    //结束一个已经开始的嵌套
    public void stopNestedScroll();
    //是否有对应的parent配合处理滑动事件
    public boolean hasNestedScrollingParent();
    //scroll的预处理过程,对应上图的0和1
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
    //scroll的后处理过程,对应上图的2和3
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    //fling和scroll和类似
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
}

NestedScrollingParent接口的源码如下:

public interface NestedScrollingParent {
    //是否接受子View的嵌套滑动
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
    //成功后的回调
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
    //结束后的回调
    public void onStopNestedScroll(View target);
    //预处理过程,对应上图的0
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    //后处理过程,对应上图的2
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
    //fling和scroll和类似
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
    //横向还是纵向
    public int getNestedScrollAxes();
}

google还提供了2个辅助类来帮助实现整个过程,分别是NestedScrollingChildHelperNestedScrollingParentHelper,从名字可以看出分别作用的对象是Child和Parent。

比如Child的用法如下:

// NestedScrollingChild

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

都是直接的调用helper的方法,可以看出google为了减少开发者的学习和开发成本费了很多心思。Parent的helper相比于Child提供的方法就不是那么多了,主要的逻辑主要靠开发者自己实现。

流程

Child Parent
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll

可以看出是一一对应的关系,整个过程是Child主导,Parent配合完成,用文字说明一下整个过程:

1. 准备过程

Child发起一个嵌套滑动处理,调用startNestedScroll.
Parent回调onStartNestedScroll说明收到了来自Child的请求,根据情况决定是否接受Child请求,通过返回值告诉Child结果。
Parent如果接受Child的请求还会回调onNestedScrollAccepted。

2. 处理过程

每次滑动前,Child 先询问 Parent 是否需要滑动,即 dispatchNestedPreScroll(),这就回调到 Parent 的 onNestedPreScroll(),Parent 可以在这个回调中“劫持”掉 Child 的滑动,也就是先于 Child 滑动。

Child 滑动以后,会调用 onNestedScroll(),回调到 Parent 的 onNestedScroll(),这里就是 Child 滑动后,剩下的给 Parent 处理,也就是 后于 Child 滑动。

3. 结束过程

Child调用stopNestedScroll发起一个结束请求,Parent回调回调onStopNestedScroll处理相关逻辑,比如重置参数等待下次滑动请求

版本兼容

虽然是Api21版本才提出的,google也提供了兼容方案,相关接口再android.support.v4包中

上一篇下一篇

猜你喜欢

热点阅读