吐槽一下开源中国居然标题字数有限制java
因为项目中使用了CoordinatorLayout来解决联动以及实现炫酷的UI效果,那么必须就要研究一波源码了,毕竟知其然知其因此然。因此这篇文章是CoordinatorLayout 学习的三部曲的开篇。android
在学习CoordinatorLayout以前,首先先来学习一下CoordinatorLayout中实现联动的关键类:NestScrollChild和NestedScrollingParent。spring
简单查看一下两个接口的定义,首先来看下NestedScrollingParent的:app
public interface NestedScrollingParent { //当前方法会在嵌套的子View调用ViewCompat.startNestedScroll(View, int)方法的时候会触发,告诉父View我要滑动了,返回值为真说明父View接收当前滑动操做 boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes); //当上面的方法返回true后,此方法会回调到 void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes); //当子View滑动中止后,该方法会回调到 void onStopNestedScroll(@NonNull View target); //当onStartNestedScroll返回true后,子View在滑动时候会回调这个方法,能够理解为ProgressBar的onProgress方法 void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed); //该方法在onNestedScroll回调以前调用,给个父View一个消费滑动的机会 void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed); //子View在进行fling时候的回调,与onNestedScroll相似,行为不一样而已 boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed); //该方法在onNestedFling回调以前调用 boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY); //当前的滑动方向 //ViewCompat#SCROLL_AXIS_HORIZONTAL //ViewCompat#SCROLL_AXIS_VERTICAL //ViewCompat#SCROLL_AXIS_NONE @ScrollAxis int getNestedScrollAxes(); }
NestedScrollingParent须要由实现滚动联动的父类实现,当实现NestedScrollingParent接口的时候,在此父类内部须要维护一个NestedScrollingParentHelper来进行触摸处理以及分发。上述方法的做用在注释中解释。ide
上述注释看起来可能仍是有点抽象,等会跟着源码(NestedScrollView)过一遍就很清晰了。post
接下来再看下NestedScrollingChild的定义:学习
public interface NestedScrollingChild { //是否启动联动 void setNestedScrollingEnabled(boolean enabled); //返回是否联动 boolean isNestedScrollingEnabled(); //开始滑动 boolean startNestedScroll(@ScrollAxis int axes); //中止滑动滑动 void stopNestedScroll(); //是否拥有滑动嵌套的父View boolean hasNestedScrollingParent(); //分发滑动事件 boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow); //分发滑动事件前的处理 boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow); //分发Fling boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed); //分发Fling前的回调 boolean dispatchNestedPreFling(float velocityX, float velocityY); }
咱们以NestedScrollView为联动的父View,NestedScrollView为子View来进行代码逻辑的分析。this
分析完后才以为不应选择上述状况分析,容易混淆,毕竟在一个NestedScrollView中回调来,回调去的spa
xml以下:code
<com.lin.aloha.photo.FatherScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" xmlns:tools="http://schemas.android.com/tools" android:layout_height="match_parent" tools:context=".photo.TestActivity"> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <View android:layout_width="match_parent" android:background="#dd2288" android:layout_margin="20dp" android:layout_height="200dp"> </View> <com.lin.aloha.photo.ChildScrollView android:layout_width="match_parent" android:background="#1d9d29" android:layout_height="400dp"> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="wrap_content"> <View android:layout_width="match_parent" android:background="#2277dd" android:layout_margin="20dp" android:layout_height="200dp"> </View> <View android:layout_width="match_parent" android:background="#2277dd" android:layout_margin="20dp" android:layout_height="200dp"> </View> <View android:layout_width="match_parent" android:background="#2277dd" android:layout_margin="20dp" android:layout_height="200dp"> </View> <View android:layout_width="match_parent" android:background="#2277dd" android:layout_margin="20dp" android:layout_height="200dp"> </View> <View android:layout_width="match_parent" android:background="#2277dd" android:layout_margin="20dp" android:layout_height="200dp"> </View> <View android:layout_width="match_parent" android:background="#2277dd" android:layout_margin="20dp" android:layout_height="200dp"> </View> </LinearLayout> </com.lin.aloha.photo.ChildScrollView> <View android:layout_width="match_parent" android:background="#dd2288" android:layout_margin="20dp" android:layout_height="200dp"> </View> </LinearLayout> </com.lin.aloha.photo.FatherScrollView>
咱们使用这个就能直接实现联动的效果,效果图以下:
NestedScrollView实现了NestedScrollingParent以及NestedScrollingChild2接口,NestedScrollingChild2就是NestedScrollingChild一些方法的拓展,核心是不变的。因为实现了这两个接口,能够说明联动的时候其能当作父View也能当作子View来处理。这里咱们首先把它当作父View来学习,既然是父View的话,天然要从dispatchTouchEvent()
开始,可是一查发现并无复写该方法,因此咱们从onInterceptTouchEvent()
看起:
public boolean onInterceptTouchEvent(MotionEvent ev) { //mIsBeingDragged标识当前View是否在移动,包括咱们触摸移动以及正在进行fling操做的时候。 final int action = ev.getAction(); if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { return true; } switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { final int y = (int) ev.getY(); ... //记录触摸的y点 mLastMotionY = y; mActivePointerId = ev.getPointerId(0); //初始化或者重置VelocityTracker initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); mScroller.computeScrollOffset(); mIsBeingDragged = !mScroller.isFinished();//若是还在滑动,则为true //调用mChildHelper.startNestedScroll() startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH); break; } case MotionEvent.ACTION_MOVE: { final int activePointerId = mActivePointerId; ... final int y = (int) ev.getY(pointerIndex); final int yDiff = Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true;//拦截事件 mLastMotionY = y; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement(ev); mNestedYOffset = 0; final ViewParent parent = getParent(); if (parent != null) { //主动告诉parent不要调用parent的interceptTouchEvent,意思是我要来消费触摸事件 parent.requestDisallowInterceptTouchEvent(true); } } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; recycleVelocityTracker(); if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } //中止滑动 stopNestedScroll(ViewCompat.TYPE_TOUCH); break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } /* * The only time we want to intercept motion events is if we are in the * drag mode. */ return mIsBeingDragged; }
onInterceptTouchEvent()事件处理比较简单:
startNestedScroll()
方法,然而该NestedScrollView因为没父View,因此该方法没意义。stopNestedScroll()
,这个方法也没意义(缘由同第一条),而后把mIsBeingDragged从新设为false。咱们再看下onTouchEvent()
事件,
public boolean onTouchEvent(MotionEvent ev) { ... switch (actionMasked) { case MotionEvent.ACTION_DOWN: { ... //获取坐标 mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0); //2.调用的NestedScrollingChild的startNestedScroll()方法 startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH); break; } case MotionEvent.ACTION_MOVE: ... final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y; //父类View不执行dispatchNestedPreScroll()方法,下面分析子View的NestedScrollView会用到 if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } mIsBeingDragged = true;//标识为true if (deltaY > 0) { deltaY -= mTouchSlop; } else { deltaY += mTouchSlop; } } if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int oldY = getScrollY(); final int range = getScrollRange(); final int overscrollMode = getOverScrollMode();//默认为OVER_SCROLL_ALWAYS boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); //最终调用scrollTo方法进行滑动 if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0, 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } final int scrolledDeltaY = getScrollY() - oldY; final int unconsumedY = deltaY - scrolledDeltaY; //一样这个方法对于父View不起效 if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, ViewCompat.TYPE_TOUCH)) { mLastMotionY -= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) {//执行当前方法 ensureGlows(); final int pulledToY = oldY + deltaY; //执行边缘效果,即到顶部或者底部出现的圆弧遮罩 if (pulledToY < 0) { EdgeEffectCompat.onPull(mEdgeGlowTop, (float) deltaY / getHeight(), ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowBottom.isFinished()) { mEdgeGlowBottom.onRelease(); } } else if (pulledToY > range) { EdgeEffectCompat.onPull(mEdgeGlowBottom, (float) deltaY / getHeight(), 1.f - ev.getX(activePointerIndex) / getWidth()); if (!mEdgeGlowTop.isFinished()) { mEdgeGlowTop.onRelease(); } } if (mEdgeGlowTop != null && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) { ViewCompat.postInvalidateOnAnimation(this); } } } break; case MotionEvent.ACTION_UP: //计算松开手后须要滑动的距离 final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } mActivePointerId = INVALID_POINTER; endDrag(); break; ... return true;//返回值为true }
从上面的步骤来看,父View彻底没有涉及到联动的操做,那么点击第一个紫红色的View,这个View在父NestedScrollView中,而且没任何触摸事件的处理,那么最终会调到父View的onTouchEvent()
中,能够看下面的log,也正是这种状况。
抛出个问题:在log中看到调用了两次的
startNestedScroll()
方法,这个会不会有什么影响呢,答案是不会的,具体的在子NestedScrollView分析
那么接下来分析一会儿NestedScrollView的行为,在父NestedScrollView中分析过,onInterceptEvent(...)
方法会调用到NestedScrollingChildHelper.startNestedScroll(...)方法,对于父NestedScrollView来讲,是没多大意义的,但对于子NestedScrollView来讲那就有意义了,NestedScrollingChildHelper.startNestedScroll(...)实现以下所示:
public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) { if (hasNestedScrollingParent(type)) { // Already in progress return true; } //是否支持嵌套滑动 if (isNestedScrollingEnabled()) { ViewParent p = mView.getParent(); View child = mView; //从子View向外查询第一个接收滑动的父View,在当前例子中就是父NestedScrollView while (p != null) { if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) { setNestedScrollingParentForType(type, p); //调用onNestedScrollAccepted()通知父View ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type); return true; } if (p instanceof View) { child = (View) p; } p = p.getParent(); } } return false; }
首先看下hasNestedScrollingParent()
方法:
public boolean hasNestedScrollingParent(@NestedScrollType int type) { return getNestedScrollingParentForType(type) != null; } private ViewParent getNestedScrollingParentForType(@NestedScrollType int type) { switch (type) { case TYPE_TOUCH: return mNestedScrollingParentTouch; case TYPE_NON_TOUCH: return mNestedScrollingParentNonTouch; } return null; }
第一次调用时候,因为mNestedScrollingParentTouch
为null,因此会接着往下走,而后在setNestedScrollingParentForType()
绑定对应的mNestedScrollingParentTouch,第二次调用时候,mNestedScrollingParentTouch
不为null,天然就不会往下走,(并且在咱们中止滑动的时候,mNestedScrollingParentTouch会设置为Null的)也就不会调用到ViewParentCompat.onStartNestedScroll()方法了。所以上面提的问题就解决了,虽然调用了两次startNestedScroll()
方法,对于代码逻辑是没有大影响的。
回到startNestedScroll()
方法中,接下来的主要有三部分组成:
isNestedScrollingEnabled()
判断是否支持嵌套滑动,支持则往下走,不然返回false上述的onStartNestedScroll(...)
以及onNestedScrollAccepted()
最终都会回调到实现NestedScrollingParent接口的相同方法名的回到当中,在这里就是回调到父NestedScrollView的对应方法中:
//NestedScrollView @Override //判断是不是垂直滑动 public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; } @Override// public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);//三级联动时会有用,这里Demo中不考虑 } //NestedScrollingParentHelper public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes) { onNestedScrollAccepted(child, target, axes, ViewCompat.TYPE_TOUCH); }
上述能够看到,咱们在触摸子NestedScrollView的时候,经过NestScrollChild2.startNestScroll(),会调用到父NestedScrollView实现NestedScrollingParent的onNestedScrollAccepted()和onStartNestedScroll()。因为子NestedScrollView的子View们不涉及触摸事件的处理,因此正当会回调到子View的onTouchEvent()
中,从新看下ACTION_DOWN方法:
case MotionEvent.ACTION_DOWN: { if (getChildCount() == 0) { return false; } if ((mIsBeingDragged = !mScroller.isFinished())) { final ViewParent parent = getParent(); if (parent != null) {//这里能够看到直接通知父NestedScrollView不拦截触摸事件 parent.requestDisallowInterceptTouchEvent(true); } } /* * If being flinged and user touches, stop the fling. isFinished * will be false if being flinged. */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // Remember where the motion event started mLastMotionY = (int) ev.getY(); mActivePointerId = ev.getPointerId(0); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH); break; }
在子NestedScrollView中的onTouchEvent()
中调用了requestDisallowInterceptTouchEvent(true)通知父NestedScrollView不拦截触摸事件,且子NestedScrollView的onTouchEvent()
永远返回true,则父NestedScrollView在当前情境下的onTouchEvent()是永远不会被调用到的。
接着继续往下看,因为onInterceptTouchEvent()
中没有涉及到对应联动的操做,这里就不看了,直接看onTouchEvent()
的ACTION_MOVE操做吧,从新贴下代码:
case MotionEvent.ACTION_MOVE: ... final int y = (int) ev.getY(activePointerIndex); int deltaY = mLastMotionY - y; //1 if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) { deltaY -= mScrollConsumed[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } ... //mIsBeingDragged为true if (mIsBeingDragged) { // Scroll to follow the motion event mLastMotionY = y - mScrollOffset[1]; final int oldY = getScrollY(); final int range = getScrollRange(); final int overscrollMode = getOverScrollMode(); boolean canOverscroll = overscrollMode == View.OVER_SCROLL_ALWAYS || (overscrollMode == View.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); //滑动该NestedScrollView if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0, 0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) { // Break our velocity if we hit a scroll barrier. mVelocityTracker.clear(); } final int scrolledDeltaY = getScrollY() - oldY; final int unconsumedY = deltaY - scrolledDeltaY; //2 if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, ViewCompat.TYPE_TOUCH)) { mLastMotionY -= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) { ...//处理边缘事件 } } break;
首先在1中, 调用mChildHelper.dispatchNestedPreScroll()方法:
public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) { if (isNestedScrollingEnabled()) { ... 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(parent, mView, dx, dy, consumed, type); ... return consumed[0] != 0 || consumed[1] != 0; } return false; } @Override public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { dispatchNestedPreScroll(dx, dy, consumed, null);//三层联动会调用到,可忽略 }
上述代码主要调用到了ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type)
方法通知父ScrollView经过consumed参数决定是否消费滑动事件,最终仍是回调会NestedScrollView.onNestedPreScroll()
回调中,因为父NestedScrollView没有parent,因此dispatchNestedPreScroll()
逻辑不会调用,至关于父NestedScrollView什么都不作。
而后在2中,调用dispatchNestedScroll()方法,方法中的uncomsued表明父NestedScrollView消费后剩下的须要子NestedScrollView滑动的距离。
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, @NestedScrollType int type) { if (isNestedScrollingEnabled()) { final ViewParent parent = getNestedScrollingParentForType(type); if (parent == null) { return false; } if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) { int startX = 0; int startY = 0; ... ViewParentCompat.onNestedScroll(parent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type); ... return true; } else if (offsetInWindow != null) { // No motion, no dispatch. Keep offsetInWindow up to date. offsetInWindow[0] = 0; offsetInWindow[1] = 0; } } return false; }
最终回调会父NestedScrollView的onNestedScroll()
中,调用scrollBy()方法进行滑动:
@Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { final int oldScrollY = getScrollY(); scrollBy(0, dyUnconsumed); ... }
接下来看下最后的ACTION_UP方法:
case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } mActivePointerId = INVALID_POINTER; endDrag(); break; private void endDrag() { mIsBeingDragged = false; recycleVelocityTracker(); stopNestedScroll(ViewCompat.TYPE_TOUCH);//调用stopNestedScroll中止滑动 if (mEdgeGlowTop != null) { mEdgeGlowTop.onRelease(); mEdgeGlowBottom.onRelease(); } }
这个ACTION_UP中相关的就只有stopNestedScroll()方法,经过调用该方法通知父NestedScrollView的onStopNestScroll()方法中:
@Override public void onStopNestedScroll(View target) { mParentHelper.onStopNestedScroll(target); ... } //重置方向参数 public void onStopNestedScroll(@NonNull View target, @NestedScrollType int type) { mNestedScrollAxes = 0; }
当咱们手指离开屏幕时候,就是fling滑动开始的时候了,也就是说在ACTION_UP方法中进行,再看下ACTION_UP方法:
case MotionEvent.ACTION_UP: final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); //速度大于最小速度,则执行Nest下发fling操做 if ((Math.abs(initialVelocity) > mMinimumVelocity)) { flingWithNestedDispatch(-initialVelocity); } else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) { ViewCompat.postInvalidateOnAnimation(this); } mActivePointerId = INVALID_POINTER; endDrag(); break; private void flingWithNestedDispatch(int velocityY) { final int scrollY = getScrollY(); final boolean canFling = (scrollY > 0 || velocityY > 0) && (scrollY < getScrollRange() || velocityY < 0); if (!dispatchNestedPreFling(0, velocityY)) {//若是父NestScrollView不消费fling,则由子NestedScrollView进行消费,通知回调onNestedFling()方法给父NestScrollView dispatchNestedFling(0, velocityY, canFling); fling(velocityY); } }
fling操做比较简单,这里就不叙述了。
总体的流程算是分析完了一遍,下面整理一张对应的流程图表示一下:
源码走一遍后发现,子View赋予了父View处理滑动的机会,这个触摸传递的模式彻底不一样,触摸事件传递是从上到下的,而上述的两个接口则提供了一种从下到上的方式,使得处理触摸事件更加的灵活。举个例子体验一下:
当子NestedScollView还没滑动到顶部或者底部时候,因此触摸事件由子NestedScrollView处理,当到顶部或者底部的时候,则借由NestedScrollChild.dispatchNestedScroll()传递给NestedScrollParent.onNestedScroll()方法处理,也就解释为何子NestedScrollView滑动到顶部时,手指再往下滑,就会滑动父NestedScrollView。
能够直接看onTouchEvent()的ACTION_MOVE方法验证正确与否:
... final int oldY = getScrollY(); ... final int scrolledDeltaY = getScrollY() - oldY; final int unconsumedY = deltaY - scrolledDeltaY; if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset, ViewCompat.TYPE_TOUCH)) { mLastMotionY -= mScrollOffset[1]; vtev.offsetLocation(0, mScrollOffset[1]); mNestedYOffset += mScrollOffset[1]; } else if (canOverscroll) { ...
当咱们滑动子NestedScrollView时候因为子NestedScrollView没有到头,scrolledDeltaY计算获取子NestedScrollView滑动距离,这时候是与deltaY的值,相等的,因此unconsumedY=0,也就是说父NestedScrollView没有消费的距离,当子NestedScrollView到头时候,scrolledDeltaY就等于0了,滑动的距离就由unconsumedY记录下来传递给父NestedScrollView,父NestedScrollView最终调用scrollTo()方法完成了滑动。
有时间的话再写一个实现Nest的例子