我们知道,安卓中View事件处理是Activity—>Window—>
DecorView—>ViewGroup—>View。当然也可以中途拦截。那么当我们在FloatingActionButton的布局文件中设置layout_anchor、layout_anchorGravity和layout_behavior时,RecyclerView的滑动事件是如何传递的呢?
以onNestedFling方法为例
case MotionEvent.ACTION_UP: {
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally ?
-VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0;
final float yvel = canScrollVertically ?
-VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
在此过程中会调用fling方法,此方法会先调用onNestedPreFling来处理,如果返回false就接着调用onNestedFling。当然不是直接调用,而是先通过调用NestedScrollingChildHelper类中相应的方法,NestedScrollingChildHelper是ViewParentCompat的代理类,ViewParentCompat会调用FloatingActionButton的父控件来最终调用我们自己Behavior类实现的方法。如下所示:
//RecyclerView中的方法
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);//mScrollingChildHelper是NestedScrollingChildHelper的实例
}
//NestedScrollingChildHelper类中的方法
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
if (isNestedScrollingEnabled()
&& mNestedScrollingParent != null) {
return ViewParentCompat.onNestedFling(mNestedScrollingParent, mView, velocityX,velocityY, consumed);
}
return false;
}//mNestedScrollingParent为mView的父控件
//ViewParentCompat中的方法
public static boolean onNestedFling(ViewParent parent, View target, float velocityX,
float velocityY, boolean consumed) {
return IMPL.onNestedFling(parent, target, velocityX, velocityY, consumed);
}
//ViewParentCompat中onNestedFling方法的实现
@Override
public boolean onNestedFling(ViewParent parent, View target, float velocityX,
float velocityY, boolean consumed) {
if (parent instanceof NestedScrollingParent) {
return ((NestedScrollingParent) parent).onNestedFling(target, velocityX, velocityY,
consumed);
}
return false;
}
从这里可以看出相应的事件已经传到父控件中了,即CoordinatorLayout类中,相关实现如下:
//CoordinatorLayout中的方法
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
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();
if (!lp.isNestedScrollAccepted()) {
continue;
}
final Behavior viewBehavior = lp.getBehavior();
if (viewBehavior != null) {
handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
consumed);
}
}
if (handled) {
dispatchOnDependentViewChanged(true);
}
return handled;
}
以前的观念中,当子控件不能处理相关事件时会返回false,交由上级处理,而FloatingActionButton则不是,它是调用父控件中相应的方法,然后父控件遍历子类并最终调用我们的实现。我们反过来想也会知道,RecyclerView是没法调用我们的Behavior实现的,通过父控件来调用才是高明之法。
同时我们也应该知道,FloatingActionButton(archor的View)的父控件必须为CoordinatorLayout,不能中间套上LinearLayout或者其他ViewGroup类的实现,因为他们并没有相应的实现,不然会发生Could not find CoordinatorLayout descendant view with id ……的错误。对于被archor的View则没有要求,从NestedScrollingChildHelper对于父控件的赋值可以看出:
//NestedScrollingChildHelper的startNestedScroll方法部分代码
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();
}
}
ViewParentCompat.onStartNestedScroll(p, child, mView, axes)会判断p是否是NestedScrollingParent的实例。代码如下:
//ViewParentCompat方类中的方法
@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;
}
所以当父控件为LinearLayout或者RelativeLayout或其他非CoordinatorLayout时会返回false,接着访问其父控件,直到碰到CoordinatorLayout,否则返回false。所以对于被archor的view具体父控件没有什么要求,只要最终是CoordinatorLayout就行。