RecyclerView源码分析

查看RecyclerView源码:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
//源码
}

其继承自ViewGroup并实现了ScrollingView和NestedScrollingChild接口,
首先研究NestedScrollingChild接口:

/**
 * 该接口应该由希望支持向父级ViewGroup分派嵌套滚动操作的子View来实现;
 * 实现此接口的类应该创建一个NestedScrollingChildHelper实例作为一个字段,并在重写的NestedScrollingChild成员方法中调用NestedScrollingChildHelper的同名方法。
 * 调用嵌套滚动功能的Views应始终从对应的ViewCompat,ViewGroupCompat或者ViewParentCompat兼容性shim静态方法执行。这确保了与Android5.0 Lollipop和更新(newer)版本的互通性。
 */
public interface NestedScrollingChild{
    /**
     * 设置嵌套滚动是否可用
     * 如果此属性设置为true,则允许该视图在当前层次结构中使用兼容的父视图启动嵌套滚动操作。 如果此视图不实现嵌套滚动,这将不起作用。 在嵌套滚动正在进行期间禁用嵌套滚动会影响stopNestedScroll()停止嵌套滚动。
     */ 
    public void setNestedScrollingEnabled(boolean enabled);
    /**
     * 返回嵌套滚动是否可用
     * 如果启用了嵌套滚动,并且此View类实现支持它,则此View将用作嵌套滚动子View(适用时),将正在进行的滚动操作的数据转发到兼容和对应的嵌套滚动父级。
     */ 
    public boolean isNestedScrollingEnabled();
    /**
     * 沿着给定的轴开始可嵌套的滚动操作,如果找到对应的父级并且已为当前手势启用嵌套滚动,则为true。
     * @param axes:由ViewCompat#SCROLL_AXIS_HORIZONTAL和/或ViewCompat#SCROLL_AXIS_VERTICAL组合的标志
     * 启动嵌套滚动的View承诺遵守以下约定:
     * 启动滚动操作时,该视图将调用startNestedScroll。 在触摸滚动的情况下,这对应于初始的MotionEvent#ACTION_DOWN。 在触摸滚动的情况下,嵌套滚动将以与ViewParent#requestDisallowInterceptTouchEvent(boolean)相同的方式自动终止。 在编程滚动的情况下,调用者必须显式地调用stopNestedScroll()来指示嵌套滚动的结束。
     * 如果startNestedScroll返回true,则找到对应的父级。 如果返回false,调用者可能忽略了该合约的其余部分,直到下一个滚动。 在嵌套滚动正在进行时调用startNestedScroll将返回true。
     * 在滚动的每个增量步骤中,一旦计算了所需的滚动增量,调用者就会调用dispatchNestedPreScroll(int,int,int [],int [])。 如果返回true,嵌套滚动父项至少部分消耗滚动,调用者应调整滚动的量。
     * 在应用滚动增量的其余部分之后,调用者应调用dispatchNestedScroll(int,int,int,int,int []),同时传递已消费的增量和未消费的增量。 嵌套滚动父项可以不同地对待这些值。 请参阅NestedScrollingParent#onNestedScroll(View,int,int,int,int)。
     */ 
    public boolean startNestedScroll(int axes);
    /**
     * 停止嵌套滑动 
     * 当嵌套滚动当前没有进行时,调用此方法是无害的。
     */ 
    public void stopNestedScroll();
    /**
     * 返回View是否有嵌套循环父级
     * 嵌套滚动父项的存在表示此View已启动嵌套滚动,并且被视图层次结构中上层次的祖先视图所接受。
     */ 
    public boolean hasNestedScrollingParent();
    /**
     * 在处理滑动之后 调用 
     * 支持嵌套滚动的Views的实现应该调用此方法报告有关正在进行滚动的信息到当前嵌套的滚动父项。 如果嵌套滚动未正在进行或嵌套滚动#isNestedScrollingEnabled()不是enabled,则此方法不执行任何操作。
     * @param dxConsumed x轴上 被消费的距离 
     * @param dyConsumed y轴上 被消费的距离 
     * @param dxUnconsumed x轴上 未被消费的距离 
     * @param dyUnconsumed y轴上 未被消费的距离 
     * @param offsetInWindow view 的移动距离 
     */ 
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
        int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    /**
     * 一般在滑动之前调用, 在ontouch 中计算出滑动距离, 然后调用该方法, 就使支持嵌套的父View处理滑动事件 
     * dispatchNestedPreScroll为嵌套滚动操作中的父View提供了一个机会,以便在子View消费之前消费部分或全部滚动操作。
     */ 
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
    /**
     * 向嵌套滚动父项分发fling事件 【hy:应该相当于触摸滑滚动后的惯性滑翔状态】
     * 这个方法应该被用来表示嵌套滚动子项已经检测到适合判定为fling状态的条件。 一般来说,这意味着触摸滚动【hy:触摸滚动 ——> 惯性滑翔】以沿滚动方向的速度结束,其沿滚动轴方向的速度满足或超过最小fling速度【hy:可以这样理解,在ACTION_UP之后,要使触摸滚动状态转换为惯性滑动即fling状态,那么需要此时的滚动速度达到临界值之上】。
     * 如果嵌套滚动子View通常会出现,但是它位于其自身内容的边缘【hy:比如当listView滑动到最顶部时】,则可以使用此方法将该轮询委托给其嵌套滚动父项代理。 父级View可以选择性地消耗fling或观察子View fling。
     */ 
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
    /**
     * 在此View处理fling状态之前,向嵌套滚动父项分发fling事件
     * 嵌套的预fling事件之于嵌套的fling事件就好比触摸拦截事件之于触摸和嵌套的预滚动之于嵌套滚动。 在子View处理事件之前,dispatchNestedPreFling将会补偿父项一次机会以充分地处理fling事件。 如果此方法返回true,则嵌套父View处理掉了此次fling事件并且子View应当不会再滚动。
     */ 
    public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}

再看看NestedScrollingChildHelper这个类:

public class NestedScrollingChildHelper {
    private final View mView;
    private ViewParent mNestedScrollingParent;
    private boolean mIsNestedScrollingEnabled;
    private int[] mTempNestedScrollConsumed;

    /**
     * Construct a new helper for a given view.
     */
    public NestedScrollingChildHelper(View view) {
        mView = view;
    }

    /**
     * 设置是否开启嵌套滚动
     * 一般在构造方法中调用
     * 这是一个委托方法。 使用同名的View子类方法/ NestedScrollingChild接口方法调用它来实现标准策略。
     */
    public void setNestedScrollingEnabled(boolean enabled) {
        if (mIsNestedScrollingEnabled) {
            ViewCompat.stopNestedScroll(mView);
        }
        mIsNestedScrollingEnabled = enabled;
    }
    public boolean isNestedScrollingEnabled() {
        return mIsNestedScrollingEnabled;
    }
    public boolean hasNestedScrollingParent() {
        return mNestedScrollingParent != null;
    }
    /**
     * 开始嵌套滑动,通知父级View协同处理本次嵌套滚动事件
     * 一般在ACTION_DOWN事件中调用
     * @param axes 滑动方向 
     * @return 是否有父view 支持嵌套滑动 
     */
    public boolean startNestedScroll(int axes) {
        if (hasNestedScrollingParent()) {
            // 如果已经找到了嵌套滑动的父View  
            // Already in progress
            return true;
        }   
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
        //递归寻找支持嵌套循环的父View,如果找到设置该父View为mNestedScrollingParent,返回true
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                    mNestedScrollingParent = p;
            // 这里回调父View的onNestedScrollAccepted方法,表示开始接收嵌套滑动  
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
         }
         return false;
     }

    /**
     * 停止嵌套滚动
     * 一般在 ACTION_CANCEL,ACTION_UP事件中调用 
     */
    public void stopNestedScroll() {
        if (mNestedScrollingParent != null) {
            ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
            mNestedScrollingParent = null;
        }
    }
   /**
     * 分发View嵌套滚动状态到父级View
     * 一般在 ACTION_MOVE事件中,当未消费触摸距离满足滚动条件时View滚动后调用 
     */
   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;
                //记录View的起始位置
                if (offsetInWindow != null) {
                    mView.getLocationInWindow(offsetInWindow);
                    startX = offsetInWindow[0];
                    startY = offsetInWindow[1];
                }
                //这里回调父View的onNestedScroll方法
                ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,dyConsumed, dxUnconsumed, dyUnconsumed);
                //计算View移动的距离
                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;
    }
    /**
     * consumed 传回被父级View消费的坐标偏移数组,0代表在对应的坐标轴上没有被父级View消费
     * 一般在onInterceptTouchEvent或者onTouchEvent的ACTION_MOVE事件中尚未对触摸事件做任何处理时调用,告知父级View触摸滑动的距离(dx,dy),这样父级View可优先消费触摸事件而将消费的距离更新到consumed数组,将子View的偏移更新到offsetInWindow数组,而dispatchNestedScroll则为已经对触摸事件部分响应后调用
     */
   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;
            //记录View的起始位置
            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;
            // 这里回调父View的onNestedPreScroll方法,父View或许会处理相应的滑动事件,  
            // 如果处理了,则consumed 会被赋予相应的值,这样接下来继续处理时,就应该在触摸位移的基础上减去这部分已经被消费的距离
            ViewParentCompat.onNestedPreScroll(mNestedScrollingParent, mView, dx, dy, consumed);
            // 父View处理了相应的滑动,  很可能导致子View的位置的移动  
            // 这里计算出父view消费滑动事件后,  导致子View的移动距离  
            if (offsetInWindow != null) {
                mView.getLocationInWindow(offsetInWindow);
                offsetInWindow[0] -= startX;
                offsetInWindow[1] -= startY;
            }
            // 如果xy方向上有不为0的表示消费了,则返回true 
            return consumed[0] != 0 || consumed[1] != 0;
        } else if (offsetInWindow != null) {
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
 }

    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
            return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX,velocityY, consumed);
        }
        return false;
    }
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
            return ViewParentCompat.onNestedPreFling(mNestedScrollingParent, mView, velocityX,velocityY);
        }
        return false;
    }
    public void onDetachedFromWindow() {
        ViewCompat.stopNestedScroll(mView);
    }
    public void onStopNestedScroll(View child) {
        ViewCompat.stopNestedScroll(mView);
    }

举个例子,ListView实现NestedScrollingChild接口,然后在onTouchEvent()中做相应的调用,应该就可以实现RecyclerView+CoordinatorLayout (extends ViewGroup implements NestedScrollingParent) 那种上划隐藏ToolBar的效果了。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值