欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

NestedScrolling(嵌套滚动)

程序员文章站 2024-03-22 23:31:16
...

关于自定义Behavior,其中还有一种是实现NestedScrolling效果的,这种效果很常见,比如像支付宝的首页,有道词典的首页等,有了这种嵌套滚动的机制可以实现很多复杂的界面效果。CoordinatorLayout就使用了这套机制,一般我们使用它直接做父View,因为它实现了NestedScrollingParent接口,那么下面我们说一说NestedScrolling。

要想了解NestedScrolling我们就得说一说NestedScrollingParentNestedScrollingChild

NestedScrollingParent

public interface NestedScrollingParent {
    /**
     *响应一个子View视图,是否执行可嵌套的滚动操作。
     *当子View视图调用了startNestedScroll(),Parent会收到onStartNestedScroll()回调,
     *决定是否需要配合Child来一起进行处理滑动,如果需要配合,还会回调onNestedScrollAccepted()。
     *当嵌套的滚动完成后,这个ViewParent将会回调onStopNestedScroll(View)。
     * @param 这个ViewParent所包含的直接子对象
     * @param 嵌套滚动的View
     * @param 嵌套滚动的方向SCROLL_AXIS_HORIZONTAL,SCROLL_AXIS_VERTICAL或者两者                         
     * @return 如果此ViewParent接受嵌套滚动操作,则返回true
     */
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

    /**
     *此方法将在onStartNestedScroll返回true之后调用
     *这个方法中可以通知需要嵌套滚动的子View
     * @param 这个ViewParent包含的直接子对象
     * @param 嵌套滚动的View
     * @param 嵌套滚动的方向SCROLL_AXIS_HORIZONTAL,SCROLL_AXIS_VERTICAL或者两者 
     *                         
     */
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

    /**
     * 
     *在嵌套滚动操作后执行资源回收等操作
     *当一个嵌套的滚动停止时,这个方法将被调用
     * @param  嵌套滚动的View
     */
    public void onStopNestedScroll(View target);

    /**
     * 当ViewParent正在进行嵌套滚动的时候,调用此方法
     * 滑动的距离消耗部分和未消耗部分都会通知给ViewParent
     * @param target 嵌套滚动的子View
     * @param dxConsumed 表示target已经消费的x方向的距离
     * @param dyConsumed 表示target已经消费的x方向的距离
     * @param dxUnconsumed 表示x方向剩下的滑动距离
     * @param dyUnconsumed 表示y方向剩下的滑动距离
     */
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);

    /**
     * 如果父视图会在嵌套滚动子View之前先滚动一段距离,那就用这个方法,也
     * 就是发生嵌套滚动之前回调
     * @param 初始化嵌套滚动的目标视图
     * @param 本次滚动产生的x方向的滚动总距离
     * @param dy 本次滚动产生的y方向的滚动总距离
     * @param 表示viewParent要消费的滚动距离,consumed[0]和consumed[1]分别表示viewParent在x和y方向上消费的距离.
     */
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

    /**
     *如果一个嵌套滚动子视图fling,但这是它位于它自己的内容的边缘,
     *那么它就可以使用这个方法将它委托给它嵌套的滚动的ViewParent。
     * ViewParent可以消费或者观察子View的fling动作
     * @param target View that initiated the nested scroll
     * @param 水平方向的速度
     * @param 垂直方向速度
     * @param 如果子View消费了fling操作,返回true,反之亦然
     * @return 如果ViewParent消费了fling或者以其他方式响应fling都返回true
     */
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);

    /**
     * 
     * 嵌套滚动之前,响应fling
     * @param target View that initiated the nested scroll
     * @param 水平方向的速度
     * @param 垂直方向速度
     * @return 如果viewParent在target之前消费了fling返回true
     */
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    /**
     * 
     *
     * @return 返回嵌套滚动的方向(标识)
     * @see ViewCompat#SCROLL_AXIS_HORIZONTAL
     * @see ViewCompat#SCROLL_AXIS_VERTICAL
     * @see ViewCompat#SCROLL_AXIS_NONE
     */
    public int getNestedScrollAxes();
}


NestedScrollingChild

public interface NestedScrollingChild {  
    /** 
     * 启用或禁用此视图的嵌套滚动
     * 如果此属性设置为true,则视图将会启动兼容ViewParent嵌套滚动的操作 
     * 
     *  @param true为启用嵌套滚动,false为禁用
     */  
    public void setNestedScrollingEnabled(boolean enabled);  

    /** 
     * 判断嵌套滑动是否可用 
     * 
     * @return 如果该视图启用嵌套滚动,则为true
     */  
    public boolean isNestedScrollingEnabled();  

    /** 
     * 沿着设定好的坐标轴方向开始嵌套滚动    
     * 该视图将在启动滚动操作时调用startNestedScroll,
     * 如果startNestedScroll返回true,那么就会寻找ViewParent
     * @param axes 表示方向轴,有横向和竖向
     */  
    public boolean startNestedScroll(int axes);  

    /** 
     * 停止正在进行的嵌套滚动
     * 当嵌套滚动不在进行中时调用此方法是没有什么卵用的。
     */  
    public void stopNestedScroll();  

    /** 
     * 如果此视图具有嵌套的滚动的ViewParent,则返回true。
     * @return whether this view has a nested scrolling parent
     */  
    public boolean hasNestedScrollingParent();  

    /** 
     * 在子View的onInterceptTouchEvent或者onTouch中,调用该方法通知父View滑动的距离
     * dispatchNestedPreScroll可以为,想在嵌套滚动中的父视图,提供了消耗部分或全部滚动操作机会。
     *(在子视图嵌套滚动消耗距离之前)
     * @param dx  x轴上滑动的距离
     * @param dy  y轴上滑动的距离
     * @param consumed 父view消费掉的scroll长度
     * @param offsetInWindow   子View的窗体偏移量
     * @return 支持的嵌套的父View 是否处理了 滑动事件 
     */  
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);  

    /** 
     * 支持嵌套滚动的视图的应该调用这个方法来报告当前滚动的信息到当前嵌套的滚动父对象
     *
     * @param dxConsumed x轴上被消费的距离(横向)
     * @param dyConsumed y轴上被消费的距离(竖向)
     * @param dxUnconsumed x轴上未被消费的距离 
     * @param dyUnconsumed y轴上未被消费的距离 
     * @param offsetInWindow 子View的窗体偏移量
     * @return  如果事件被发送,则为true,如果没能发送,则为false。
     */  
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,  
          int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);  



    /** 
     * 滑行时调用 
     *
     * @param velocityX x 轴上的滑动速率
     * @param velocityY y 轴上的滑动速率
     * @param consumed 如果孩子消费了这个Fling,则为true,否则为false
     * @return  如果嵌套的滚动父级消耗或以以其他方式对Fling做出反应,则返回true
     */  
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);  

    /** 
     * 在这个视图处理之前,将一个fling分发给一个嵌套滚动的父级。
     *
     * @param velocityX x 轴上的滑动速率
     * @param velocityY y 轴上的滑动速率 
     * @return 如果嵌套的滚动父级消费了该Fling,则为true
     */  
    public boolean dispatchNestedPreFling(float velocityX, float velocityY);  
}


这么多的方法和参数,看这真是晕~但实际上,我们不会直接使用这两个接口的。而是使用 NestedScrollingChildHelper或NestedScrollingParentHelper这个辅助类,Android已经实现好了 Child 和 Parent 交互的逻辑。

实现 NestedScrollingChild

如果你有一个滑动的View(自定义或原生控件)需要被用来作为嵌入滑动的子 View,就必须实现本接口。例如:ListView

大体的流程如下:

1.子View需要调用setNestedScrollingEnabled启动嵌套滑动,然后调用startNestedScroll方法,让他去寻找viewParent,
通知ViewParent.

2.然后在子ViewonInterceptTouchEvent或者onTouch中,调用dispatchNestedPreScroll来通知ViewParent需不需要滑动,如果ViewParent要滑动,那么根据方法的3,4参数,返回父view消费掉的scroll长度和子View的窗体偏移量,如果ViewParent没有把所有的距离消耗完,那么子View就可以处理 剩下的距离了。

3.子View调用dispatchNestedScroll方法向ViewParent通知滚动情况,包括子view消费的部分和子view没有消费的部分。
如果父view接受了它的滚动参数,进行了部分消费,则这个函数返回true,否则为false。

4.调用stopNestedScroll

CoordinatorLayout就实现了NestedScrollingParent

那么说到嵌套滚动,Behavior在这里面是干啥的呢?下面看看在源码里面做了什么

//滑动开始的调用startNestedScroll(),ViewParent收到onStartNestedScroll() 回调, 
@Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == View.GONE) {
                // If it's GONE, don't dispatch
                continue;

            //决定是否需要配合 Child 一起进行处理滑动,如果需要配合,还会回调 onNestedScrollAccepted()。
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

                lp.acceptNestedScroll(accepted);
            } else {
                lp.acceptNestedScroll(false);
            }
        }
        return handled;
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
        mNestedScrollingDirectChild = child;
        mNestedScrollingTarget = target;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
            }
        }
    }

    @Override
    public void onStopNestedScroll(View target) {
        mNestedScrollingParentHelper.onStopNestedScroll(target);

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onStopNestedScroll(this, view, target);
            }
            lp.resetNestedScroll();
            lp.resetChangedAfterNestedScroll();
        }

        mNestedScrollingDirectChild = null;
        mNestedScrollingTarget = null;
    }
    //Child 滑动以后,会调用 onNestedScroll(),回调到 ViewParent 的 onNestedScroll(),
   //这里就是 Child 滑动后,剩下的给 ViewParent 处理,也就是后于 Child 滑动。
    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed) {
        final int childCount = getChildCount();
        boolean accepted = false;

        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
                        dxUnconsumed, dyUnconsumed);
                accepted = true;
            }
        }

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }
    //ViewParent 可以在这个回调中消费滚动的距离
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        int xConsumed = 0;
        int yConsumed = 0;
        boolean accepted = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);

                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                        : Math.min(xConsumed, mTempIntPair[0]);
                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                        : Math.min(yConsumed, mTempIntPair[1]);

                accepted = true;
            }
        }

        consumed[0] = xConsumed;
        consumed[1] = yConsumed;

        if (accepted) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
                        consumed);
            }
        }
        if (handled) {
            onChildViewsChanged(EVENT_NESTED_SCROLL);
        }
        return handled;
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            if (view.getVisibility() == GONE) {
                // If the child is GONE, skip...
                continue;
            }

            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
            }
        }
        return handled;
    }

    @Override
    public int getNestedScrollAxes() {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }


当CoordiantorLayout接收到了NestedScrollingChild的回调后,把事件交给了Behavior来处理的,
用户来继承CoordiantorLayout.Behavior的时候,这个Behavior其实是个NestedScrollingParent ,
而且还是个加强版的。


以前练习的demo找不到了,等下次重新写一个吧。
隔壁HMI的人,天天在吵吵,头痛( ⊙ o ⊙ )啊!如果这些HMI团队的人是那种妹子团的,我也就忍了。可是这种老男人+大姐的这种团队,就搞的你是真不爽了。

相关标签: 界面