吐槽一下開源中國居然標題字數有限制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的例子