CoordinatorLayout、Behavior和嵌套滑动(
嵌套滑动
一个CoordinatorLayout嵌套NestedScrollView :点击之后弹起
首先调用CoordinatorLayout的onInterceptTouchEvent()函数,返回false。之后调用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的范围内,如果在,则继续。这里用到VelocityTracker这个类,主要用于通过跟踪一连串事件实时计算出当前的速度。在这次点击中NestedScrollView并没有在滚动,所以mScroller.isFinished()是返回true的,所以mIsBeingDragged为false,此次动作没有拦截。在返回之前调用了startNestedScroll()这个方法。
@Override
public boolean startNestedScroll(int axes) {
return mChildHelper.startNestedScroll(axes);
}
可以看到在这里调用了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;
}
最开始先判断是否有可以进行嵌套滑动的父布局,只要正在进行嵌套滑动,这个函数就会返回true,但一开始是返回false的。之后有一个循环,寻找是否有可以处理嵌套滑动的父布局。在循环过程中,child一直是parent的直接子View,而且child一直包含mView。
public boolean hasNestedScrollingParent() {
return mNestedScrollingParent != null;
}
之后调用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是触发嵌套滑动的可滚动的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, 所以最终调用了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是否设置了Behavior属性,如果都没有,则直接返回false。这里我们的子View没有设置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;
}
......
同样也会调用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();
}
}
调用了stopNestedScroll()。
@Override
public void stopNestedScroll() {
mChildHelper.stopNestedScroll();
}
NestedScrollingChildHelper。
public void stopNestedScroll() {
if (mNestedScrollingParent != null) {
ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
mNestedScrollingParent = null;
}
}
这里mNestedScrollingParent为空,所以不会调用到CoordinatorLayout的onNestedStopScroll()。
NestedScrollView在onInterceptTouchEvent()和onTouchEvent()中都会调用startNestedScroll(),最后会调用到CoordinatorLayout的onStartNestedScroll()。但是如果CoordinatorLayout的子View都没有设置Behavior,则什么都不会发生。