嵌套滑動
一個CoordinatorLayout嵌套NestedScrollView :點擊之后彈起
首先調(diào)用CoordinatorLayout的onInterceptTouchEvent()函數(shù)语卤,返回false。之后調(diào)用NestedScrollView的onInterceptTouchEvent()霜威。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
......
case MotionEvent.ACTION_DOWN: {
final int y = (int) ev.getY();
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionY = y;
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged. We need to call computeScrollOffset() first so that
* isFinished() is correct.
*/
mScroller.computeScrollOffset();
mIsBeingDragged = !mScroller.isFinished();
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
}
......
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
這里先看CoordinatorLayout對 ACTION_DOWN的處理谈喳。首先判斷該觸摸點是否在NestedScrollView的范圍內(nèi),如果在戈泼,則繼續(xù)婿禽。這里用到VelocityTracker這個類,主要用于通過跟蹤一連串事件實時計算出當前的速度大猛。在這次點擊中NestedScrollView并沒有在滾動扭倾,所以mScroller.isFinished()是返回true的,所以mIsBeingDragged為false挽绩,此次動作沒有攔截膛壹。在返回之前調(diào)用了startNestedScroll()這個方法。
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
可以看到在這里調(diào)用了NestedScrollingChildHelper?的startNestedScroll()方法唉堪。
public boolean startNestedScroll(int axes) {
if (hasNestedScrollingParent()) {
// Already in progress
return true;
}
// 尋找是否有可以進行嵌套滑動的父布局
if (isNestedScrollingEnabled()) {
ViewParent p = mView.getParent();
View child = mView;
while (p != null) {
if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
mNestedScrollingParent = p;
ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
return true;
}
if (p instanceof View) {
child = (View) p;
}
p = p.getParent();
}
}
return false;
}
最開始先判斷是否有可以進行嵌套滑動的父布局模聋,只要正在進行嵌套滑動,這個函數(shù)就會返回true唠亚,但一開始是返回false的链方。之后有一個循環(huán),尋找是否有可以處理嵌套滑動的父布局灶搜。在循環(huán)過程中祟蚀,child一直是parent的直接子View,而且child一直包含mView割卖。
public boolean hasNestedScrollingParent() {
return mNestedScrollingParent != null;
}
之后調(diào)用ViewParentCompat.onStartNestedScroll(p, child, mView, axes)前酿。如果返回true,則該可滾動的視圖有可嵌套滑動的父布局究珊。
public static boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
return IMPL.onStartNestedScroll(parent, child, target, nestedScrollAxes);
}?
這里的target是觸發(fā)嵌套滑動的可滾動的View薪者,chid是Parent的直接子View,它包含target剿涮。這里我們NestedScrollView直接就是CoordinatorLayout的子View言津,所以child和target是同一個攻人。IMPL用來處理兼容性。
static final ViewParentCompatImpl IMPL;
static {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
IMPL = new ViewParentCompatLollipopImpl();
} else if (version >= 19) {
IMPL = new ViewParentCompatKitKatImpl();
} else if (version >= 14) {
IMPL = new ViewParentCompatICSImpl();
} else {
IMPL = new ViewParentCompatStubImpl();
}
}
這里來看其中一個就好:
static class ViewParentCompatStubImpl implements ViewParentCompatImpl {
@Override
public boolean requestSendAccessibilityEvent(
ViewParent parent, View child, AccessibilityEvent event) {
// Emulate what ViewRootImpl does in ICS and above.
if (child == null) {
return false;
}
final AccessibilityManager manager = (AccessibilityManager) child.getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
manager.sendAccessibilityEvent(event);
return true;
}
@Override
public boolean onStartNestedScroll(ViewParent parent, View child, View target,
int nestedScrollAxes) {
if (parent instanceof NestedScrollingParent) {
return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
nestedScrollAxes);
}
return false;
}
......
先看onStartNestedScroll()這個方法悬槽。這里面判斷parent是不是NestedScrollingParent的一個實例怀吻。因為CoordinatorLayout繼承了NestedScrollingParent, 所以最終調(diào)用了CoordinatorLayout的onStartNestedScroll()方法。
public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent{
......
}
CoordinatorLayout的onStartNestedScroll()方法:
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);
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;
}
在onStartNestedScroll()方法中會判斷子View的LayoutParams是否設(shè)置了Behavior屬性初婆,如果都沒有蓬坡,則直接返回false。這里我們的子View沒有設(shè)置Behavior磅叛,所以返回了false屑咳。
之后動作傳到onTouchEvent()。
@Override
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
MotionEvent vtev = MotionEvent.obtain(ev);
final int actionMasked = MotionEventCompat.getActionMasked(ev);
if (actionMasked == MotionEvent.ACTION_DOWN) {
mNestedYOffset = 0;
}
vtev.offsetLocation(0, mNestedYOffset);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
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 = MotionEventCompat.getPointerId(ev, 0);
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
}
......
同樣也會調(diào)用startNestedScroll(), 之后的流程和上面的一樣了弊琴,但是這里onTouchEvent()會返回true兆龙。到最后是ACTION_UP。
......
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker,
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;
......
這里mIsBeingDragged一直為false敲董,先不管紫皇。看endDrag()腋寨。
private void endDrag() {
mIsBeingDragged = false;
recycleVelocityTracker();
stopNestedScroll();
if (mEdgeGlowTop != null) {
mEdgeGlowTop.onRelease();
mEdgeGlowBottom.onRelease();
}
}
調(diào)用了stopNestedScroll()聪铺。
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
NestedScrollingChildHelper?。
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
mNestedScrollingParent = null;
}
}
這里mNestedScrollingParent為空萄窜,所以不會調(diào)用到CoordinatorLayout的onNestedStopScroll()铃剔。
NestedScrollView在onInterceptTouchEvent()和onTouchEvent()中都會調(diào)用startNestedScroll(),最后會調(diào)用到CoordinatorLayout的onStartNestedScroll()脂倦。但是如果CoordinatorLayout的子View都沒有設(shè)置Behavior番宁,則什么都不會發(fā)生。