• 学习Android NestedScroll


    NestedScrollingChildHelper

    这是一个用于实现子视图嵌套滚动的辅助类,并提供对Android 5.0之前版本的前兼容。

    View要作为嵌套滚动中的Child,要在构造方法中实例化一个final的NestedScrollingChildHelper对象。该View中有一系列方法签名(即方法名和参数列表)和此类中相同的方法(这些方法实际来自于View实现的NestedScrollingChild接口),通过委托模式(delegate),这些方法的实际实现被委派给helper对象来完成。这提供了一种标准的结构化策略,来实现嵌套滚动。

    例如,下面是NestedScrollView中相关的代码:

    public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
            NestedScrollingChild {
        ......
        private final NestedScrollingParentHelper mParentHelper;
        // 这里文档中特别强调需要final修饰,而且要在构造方法中实例化
        private final NestedScrollingChildHelper mChildHelper;
    
    public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            ......
            mParentHelper = new NestedScrollingParentHelper(this);
            mChildHelper = new NestedScrollingChildHelper(this);
            ......
        }
    
        // 下面9个方法来自于NestedScrollingChild接口,注意他们只是简单调用了mChildHelper中同名同参数列表的方法。
    
        @Override
        public void setNestedScrollingEnabled(boolean 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);
        }
    
        ......
    }
    

    使用嵌套滚动功能的视图,应该始终使用ViewCompat, ViewGroupCompat 或者 ViewParentCompat中相关的兼容性静态方法。这保证了和Android 5.0之后的嵌套滚动视图之间互操作的正确性。

    下面来具体的看这九个方法在NestedScrollingChildHelper类中的实现:

    1. 使能或禁止Child的嵌套滚动。

       public void setNestedScrollingEnabled(boolean enabled) {
           // 如果处于使能状态,有可能正在滚动,需要先停止。
           if (mIsNestedScrollingEnabled) {
               // 相当于调用View.stopNestedScroll()。
               // 这个方法如果View正在嵌套滚动,会停止嵌套滚动,反之无影响。
               // 注意这里实际还是会调用到NestedScrollingChildHelper.stopNestedScroll()。
               ViewCompat.stopNestedScroll(mView);
           }
           // 这个标志位代表能否嵌套滚动。
           mIsNestedScrollingEnabled = enabled;
       }
      
    2. 返回是否可以嵌套滚动。

       // 如果这里返回true,说明相应的子视图可以嵌套滚动。
       // 那么helper类会负责将滚动操作过程中的相关数据传递给相关联的nested scrolling parent。
       public boolean isNestedScrollingEnabled() {
           return mIsNestedScrollingEnabled;
       }
      
    3. 检查是否有关联的父视图,来接受嵌套滚动过程中的事件。

       // 注意这里的mNestedScrollingParent初始化时是null,
       // 是在下面的startNestedScroll(int axes)方法中找到的。
       public boolean hasNestedScrollingParent() {
           return mNestedScrollingParent != null;
       }
      
    4. 开始一个新的嵌套滚动。

       // 参数axes代表滚动轴,取ViewCompat#SCROLL_AXIS_HORIZONTAL and/or ViewCompat#SCROLL_AXIS_VERTICAL。
       // 如果找到了嵌套滚动的parent view,并且对于当前手势使能了嵌套滚动,返回true。
       public boolean startNestedScroll(int axes) {
           if (hasNestedScrollingParent()) {
               // 已经在嵌套滚动过程中。
               return true;
           }
           // 嵌套滚动功能使能情况下才能开始。
           if (isNestedScrollingEnabled()) {
               ViewParent p = mView.getParent();
               View child = mView;
               // 依次找父视图,直到找到第一个接受嵌套滚动的父视图。
               while (p != null) {
                   // 实际调用的是NestedScrollingParent中的
                   // onStartNestedScroll(View child, View target, int nestedScrollAxes)方法。
                   // 返回父视图是否接受此嵌套滚动操作。
                   if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                       mNestedScrollingParent = p;
                       // 这里实际会调用NestedScrollingParentHelper.onNestedScrollAccepted(View child, View target, int axes)方法。
                       ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                       return true;
                   }
                   if (p instanceof View) {
                       child = (View) p;
                   }
                   p = p.getParent();
               }
           }
           return false;
       }
      
    5. 停止嵌套滚动

       public void stopNestedScroll() {
           if (mNestedScrollingParent != null) {
               //  实际调用的是NestedScrollingParentHelper.onStopNestedScroll(View target)。
               ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
               mNestedScrollingParent = null;
           }
       }
      
    6. 将嵌套滚动过程中的一步分发给当前的nested scrolling parent。

       // 返回true代表该事件被分发(即父视图消费任意的嵌套滚动事件),反之则是无法被分发。
       public boolean dispatchNestedScroll(
       	int dxConsumed, // 滚动中被此view消费的水平距离
       	int dyConsumed, // 滚动中被此view消费的垂直距离
           int dxUnconsumed, // 滚动中未被此view消费的水平距离
       	int dyUnconsumed, // 滚动中未被此view消费的垂直距离
       	int[] offsetInWindow // 滚动前后此view的坐标偏移量
       	) {
           if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
               if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
                   int startX = 0;
                   int startY = 0;
                   if (offsetInWindow != null) {
                       mView.getLocationInWindow(offsetInWindow);
                       startX = offsetInWindow[0];
                       startY = offsetInWindow[1];
                   }
      
                   // 此方法来自NestedScrollingParent.onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
                   // 具体实现由父视图完成。
                   ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
                           dyConsumed, dxUnconsumed, dyUnconsumed);
      
                   if (offsetInWindow != null) {
                       mView.getLocationInWindow(offsetInWindow);
                       // 偏移量是滚动后的位置减滚动前的位置
                       offsetInWindow[0] -= startX;
                       offsetInWindow[1] -= startY;
                   }
                   return true;
               } else if (offsetInWindow != null) {
                   // No motion, no dispatch. Keep offsetInWindow up to date.
                   offsetInWindow[0] = 0;
                   offsetInWindow[1] = 0;
               }
           }
           return false;
       }
  • 相关阅读:
    Xamarin Andro教程搭建Xamarin Androidid开发环境(一)
    判断密文加密类型hash-identifier
    Xamarin Android教程如何使用Xamarin开发Android应用
    分享Kali Linux 2016.2第48周虚拟机
    Xamarin Android教程Android基本知识版本介绍与系统介绍
    iOS10 UI教程视图的生命周期
    iOS10 UI教程视图的绘制与视图控制器和视图
    卡尔曼滤波的原理说明
    Bresenham算法
    Stm32 Bootloader整理
  • 原文地址:https://www.cnblogs.com/yuanchongjie/p/4981626.html
Copyright © 2020-2023  润新知