CoordinatorLayout里Behavior简单分析

当给CoordinatorLayout的直接子View设置Behavior的时候,CoordinatorLayout里面的一些事件就可以传入到Behavior,Behavior又可以指导View做一些相应的处理。

Behavior对象是怎么被实例化的

CoordinatorLayout里面的子View是怎么拿到Behavior对象的。设置Bevior有两种方式
1. 代码里面直接设置(LayoutParams)child.getLayoutParams().setBehavior()这个就好说了。
2. XML里面去设置app:layout_behavior=”” 。

对应第二种情况Behavior是怎么实例化的。
CoordinatorLayout里面LayoutParams两个参数的构造函数。

LayoutParams(Context context, AttributeSet attrs) {
    super(context, attrs);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.CoordinatorLayout_LayoutParams);

    this.gravity = a.getInteger(
            R.styleable.CoordinatorLayout_LayoutParams_android_layout_gravity,
            Gravity.NO_GRAVITY);
    mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_LayoutParams_layout_anchor,
            View.NO_ID);
    this.anchorGravity = a.getInteger(
            R.styleable.CoordinatorLayout_LayoutParams_layout_anchorGravity,
            Gravity.NO_GRAVITY);

    this.keyline = a.getInteger(R.styleable.CoordinatorLayout_LayoutParams_layout_keyline,
            -1);

    mBehaviorResolved = a.hasValue(
            R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
    if (mBehaviorResolved) {
        mBehavior = parseBehavior(context, attrs, a.getString(
                R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
    }

    a.recycle();
}

第20行是否设置了app:layout_behavior=”” 如果设置了调用parseBehavior函数。利用反射去实例化出Behavior对象。

Behavior简单分析

Behavior都是配合CoordinatorLayout来使用的。
对于Behavior我们分三种情况来考虑。
1. Behavior的onInterceptTouchEvent + onTouchEvent。
2. Behavior的layoutDependsOn + onDependentViewChanged + onDependentViewRemoved。View引起的变化。
3. Behavior的onStartNestedScroll + onNestedScrollAccepted + onStopNestedScroll + onNestedScroll + onNestedPreScroll + onNestedFling + onNestedPreFling。嵌套滑动引起的变化。
大概的去知道每种情况里面每个函数的调用时机。和大概的作用。

Behavior的onInterceptTouchEvent + onTouchEvent

分两部分来考虑。
1. CoordinatorLayout里面是怎么调用到我们指定View的Behavior的onInterceptTouchEvent和onTouchEvent里面去的。
2. View的Behavior里面onInterceptTouchEvent和onTouchEvent里面干了什么事情。(这个一般是我们自定义Behavior的时候处理,这个先不管)。

CoordinatorLayout里面是怎么调用到我们指定View的Behavior的onInterceptTouchEvent和onTouchEvent里面去的。直接CoordinatorLayout的onInterceptTouchEvent函数和onTouchEvent函数里面了先CoordinatorLayout类的onInterceptTouchEvent函数。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    // Make sure we reset in case we had missed a previous important event.
    if (action == MotionEvent.ACTION_DOWN) {
        resetTouchBehaviors();
    }

    final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

    if (cancelEvent != null) {
        cancelEvent.recycle();
    }

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        resetTouchBehaviors();
    }

    return intercepted;
}

第12行调用了performIntercept函数,参数type TYPE_ON_INTERCEPT表示从onInterceptTouchEvent函数进来的,TYPE_ON_TOUCH表示从onTouchEvent函数进来的。

private boolean performIntercept(MotionEvent ev, final int type) {
    boolean intercepted = false;
    boolean newBlock = false;

    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    final List<View> topmostChildList = mTempList1;
    getTopSortedChildren(topmostChildList);

    // Let topmost child views inspect first
    final int childCount = topmostChildList.size();
    for (int i = 0; i < childCount; i++) {
        final View child = topmostChildList.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior b = lp.getBehavior();

        if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
            // Cancel all behaviors beneath the one that intercepted.
            // If the event is "down" then we don't have anything to cancel yet.
            if (b != null) {
                if (cancelEvent == null) {
                    final long now = SystemClock.uptimeMillis();
                    cancelEvent = MotionEvent.obtain(now, now,
                            MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                }
                switch (type) {
                    case TYPE_ON_INTERCEPT:
                        b.onInterceptTouchEvent(this, child, cancelEvent);
                        break;
                    case TYPE_ON_TOUCH:
                        b.onTouchEvent(this, child, cancelEvent);
                        break;
                }
            }
            continue;
        }

        if (!intercepted && b != null) {
            switch (type) {
                case TYPE_ON_INTERCEPT:
                    intercepted = b.onInterceptTouchEvent(this, child, ev);
                    break;
                case TYPE_ON_TOUCH:
                    intercepted = b.onTouchEvent(this, child, ev);
                    break;
            }
            if (intercepted) {
                mBehaviorTouchView = child;
            }
        }

        // Don't keep going if we're not allowing interaction below this.
        // Setting newBlock will make sure we cancel the rest of the behaviors.
        final boolean wasBlocking = lp.didBlockInteraction();
        final boolean isBlocking = lp.isBlockingInteractionBelow(this, child);
        newBlock = isBlocking && !wasBlocking;
        if (isBlocking && !newBlock) {
            // Stop here since we don't have anything more to cancel - we already did
            // when the behavior first started blocking things below this point.
            break;
        }
    }

    topmostChildList.clear();

    return intercepted;
}

第14行开始,遍历CoordinatorLayout所有的View,第19行第一个if做的事情是如果现在已经有View对应的Behavoir拦截了的,并且不是ACTION_DOWN的时候,其他View的Behavoir都会收到ACTION_CANCEL事件。
第40行第二个if如果没有被拦截并且有Behavoir则调用Behavoir对应的函数onInterceptTouchEvent或者onTouchEvent函数。

CoordinatorLayout的onTouchEvent函数

@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean handled = false;
    boolean cancelSuper = false;
    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
        // Safe since performIntercept guarantees that
        // mBehaviorTouchView != null if it returns true
        final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
        final Behavior b = lp.getBehavior();
        if (b != null) {
            handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
        }
    }

    // Keep the super implementation correct
    if (mBehaviorTouchView == null) {
        handled |= super.onTouchEvent(ev);
    } else if (cancelSuper) {
        if (cancelEvent != null) {
            final long now = SystemClock.uptimeMillis();
            cancelEvent = MotionEvent.obtain(now, now,
                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
        }
        super.onTouchEvent(cancelEvent);
    }

    if (!handled && action == MotionEvent.ACTION_DOWN) {

    }

    if (cancelEvent != null) {
        cancelEvent.recycle();
    }

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        resetTouchBehaviors();
    }

    return handled;
}

CoordinatorLayout的onTouchEvent函数就是看下当前事件是不是有哪个View的Behavior感兴趣。如果感兴趣就给View的Behavior做处理,不敢兴趣就给CoordinatorLayout自己处理。
这样Behavior的onInterceptTouchEvent + onTouchEvent。两个函数的调用也引导进去了。

Behavior的layoutDependsOn + onDependentViewChanged + onDependentViewRemoved
    /**
     * child是否要依赖dependency
     * @param parent     CoordinatorLayout
     * @param child      该Behavior对应的那个View
     * @param dependency 要检查的View(child是否要依赖这个dependency)
     * @return true 依赖, false 不依赖
     */
    public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
        return false;
    }

    /**
     * 在layoutDependsOn返回true的基础上之后,及时报告dependency的状态变化
     * @param parent     CoordinatorLayout
     * @param child      该Behavior对应的那个View
     * @param dependency child依赖dependency
     * @return true 处理了, false  没处理
     */
    public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
        return false;
    }

    /**
     * 在layoutDependsOn返回true的基础上之后,报告dependency被移除了
     * @param parent     CoordinatorLayout
     * @param child      该Behavior对应的那个View
     * @param dependency child依赖dependency
     */
    public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
    }

所有的源头都在CoordinatorLayout类里面
CoordinatorLayout类里面onAttachedToWindow

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    resetTouchBehaviors();
    if (mNeedsPreDrawListener) {
        if (mOnPreDrawListener == null) {
            mOnPreDrawListener = new OnPreDrawListener();
        }
        final ViewTreeObserver vto = getViewTreeObserver();
        vto.addOnPreDrawListener(mOnPreDrawListener);
    }
    if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
        // We're set to fitSystemWindows but we haven't had any insets yet...
        // We should request a new dispatch of window insets
        ViewCompat.requestApplyInsets(this);
    }
    mIsAttachedToWindow = true;
}

第10行给ViewTreeObserver添加了一个OnPreDrawListener的监听。OnPreDrawListener代码如下

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
    @Override
    public boolean onPreDraw() {
        dispatchOnDependentViewChanged(false);
        return true;
    }
}

OnPreDrawListener实现了ViewTreeObserver.OnPreDrawListener接口重写了onPreDraw函数,onPreDraw在每次draw的时候都会调用。就是在CoordinatorLayout每次重绘的时候调用。View状态发生变化的时候调用。这样重点就到了dispatchOnDependentViewChanged函数了参数false表示不是嵌套滑动引起的变化是View状态改变引起的变化。

void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    final int childCount = mDependencySortedChildren.size();
    for (int i = 0; i < childCount; i++) {
        final View child = mDependencySortedChildren.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        // Check child views before for anchor
        for (int j = 0; j < i; j++) {
            final View checkChild = mDependencySortedChildren.get(j);

            if (lp.mAnchorDirectChild == checkChild) {
                offsetChildToAnchor(child, layoutDirection);
            }
        }

        // Did it change? if not continue
        final Rect oldRect = mTempRect1;
        final Rect newRect = mTempRect2;
        getLastChildRect(child, oldRect);
        getChildRect(child, true, newRect);
        if (oldRect.equals(newRect)) {
            continue;
        }
        recordLastChildRect(child, newRect);

        // Update any behavior-dependent views for the change
        for (int j = i + 1; j < childCount; j++) {
            final View checkChild = mDependencySortedChildren.get(j);
            final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
            final Behavior b = checkLp.getBehavior();

            if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
                    // If this is not from a nested scroll and we have already been changed
                    // from a nested scroll, skip the dispatch and reset the flag
                    checkLp.resetChangedAfterNestedScroll();
                    continue;
                }

                final boolean handled = b.onDependentViewChanged(this, checkChild, child);

                if (fromNestedScroll) {
                    // If this is from a nested scroll, set the flag so that we may skip
                    // any resulting onPreDraw dispatch (if needed)
                    checkLp.setChangedAfterNestedScroll(handled);
                }
            }
        }
    }
}

第3行 mDependencySortedChildren里面放的是CoordinatorLayout所有的子View,只不过把相关连的View放到一起了(相关联有两种一种是直接xml里面设置了layout_anchor,一种是Behavior里面设置了关联layoutDependsOn函数)。接着遍历所有的View。
第9行到15行找到哪个View设置了layout_anchor也就是说设置显示坐标的锚点 哪个View的layout_anchor对应child。调整位置,这个应该好理解点layout_anchor设置的View和当前的View是相关会一起变化的。
第28行到49行找到哪个View的Behavior depend on child这个View。第0行调用了Behavior的layoutDependsOn判断是否依赖。如果依赖继续调用Behavior的onDependentViewChanged函数。

到这里Behavior的layoutDependsOn和onDependentViewChanged的调用的地方和调用的时机我们都知道了。还差一个onDependentViewRemoved函数。继续看CoordinatorLayout里面的HierarchyChangeListener里 在哪里用到了呢构造函数里面super.setOnHierarchyChangeListener(new HierarchyChangeListener());设置了去监听ViewGroup中的View的层次变化当View removed掉的时候这里能够监听到了会调用onChildViewRemoved。

final class HierarchyChangeListener implements OnHierarchyChangeListener {
    @Override
    public void onChildViewAdded(View parent, View child) {
        if (mOnHierarchyChangeListener != null) {
            mOnHierarchyChangeListener.onChildViewAdded(parent, child);
        }
    }

    @Override
    public void onChildViewRemoved(View parent, View child) {
        dispatchDependentViewRemoved(child);

        if (mOnHierarchyChangeListener != null) {
            mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
        }
    }
}

继续dispatchDependentViewRemoved函数。

void dispatchDependentViewRemoved(View removedChild) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior b = lp.getBehavior();

        if (b != null && b.layoutDependsOn(this, child, removedChild)) {
            b.onDependentViewRemoved(this, child, removedChild);
        }
    }
}

找到对应View的Behavior 如果是layoutDependsOn的就去调用Behavior的onDependentViewRemoved。
到此Behavior的layoutDependsOn + onDependentViewChanged + onDependentViewRemoved三个函数的调用时机和调用顺序都结束了。

Behavior的onStartNestedScroll + onNestedScrollAccepted + onStopNestedScroll + onNestedScroll + onNestedPreScroll + onNestedFling + onNestedPreFling
    /**
     * 有嵌套滑动到来了,问下该Behavior是否接受嵌套滑动
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             该Behavior对应的View
     * @param directTargetChild 嵌套滑动对应的父类的子类(因为嵌套滑动对于的父View不一定是一级就能找到的,可能挑了两级父View的父View, directTargetChild>=target)
     * @param target            具体嵌套滑动的那个子类
     * @param nestedScrollAxes  支持嵌套滚动轴。水平方向,垂直方向,或者不指定
     * @return 是否接受该嵌套滑动
     */
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout,
                                       V child,
                                       View directTargetChild,
                                       View target,
                                       int nestedScrollAxes) {
        return false;
    }

    /**
     * Behavior接受了嵌套滑动的请求该函数调用。onStartNestedScroll返回true该函数会被调用。 参数和onStartNestedScroll一样
     */
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout,
                                       V child,
                                       View directTargetChild,
                                       View target,
                                       int nestedScrollAxes) {
        // Do nothing
    }

    /**
     * 停止嵌套滑动
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             该Behavior对应的View
     * @param target            具体嵌套滑动的那个子类
     */
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
        // Do nothing
    }

    /**
     * 嵌套滑动的子View在滑动之后报告过来的滑动情况
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             该Behavior对应的View
     * @param target            具体嵌套滑动的那个子类
     * @param dxConsumed        水平方向嵌套滑动的子View滑动的距离(消耗的距离)
     * @param dyConsumed        垂直方向嵌套滑动的子View滑动的距离(消耗的距离)
     * @param dxUnconsumed      水平方向嵌套滑动的子View未滑动的距离(未消耗的距离)
     * @param dyUnconsumed      垂直方向嵌套滑动的子View未滑动的距离(未消耗的距离)
     */
    public void onNestedScroll(CoordinatorLayout coordinatorLayout,
                               V child,
                               View target,
                               int dxConsumed,
                               int dyConsumed,
                               int dxUnconsumed,
                               int dyUnconsumed) {
        // Do nothing
    }

    /**
     * 在嵌套滑动的子View未滑动之前告诉过来的准备滑动的情况
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             该Behavior对应的View
     * @param target            具体嵌套滑动的那个子类
     * @param dx                水平方向嵌套滑动的子View想要变化的距离
     * @param dy                垂直方向嵌套滑动的子View想要变化的距离
     * @param consumed          这个参数要我们在实现这个函数的时候指定,回头告诉子View当前父View消耗的距离 consumed[0] 水平消耗的距离,consumed[1] 垂直消耗的距离 好让子view做出相应的调整
     */
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
        // Do nothing
    }

    /**
     * 嵌套滑动的子View在fling之后报告过来的fling情况
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             该Behavior对应的View
     * @param target            具体嵌套滑动的那个子类
     * @param velocityX         水平方向速度
     * @param velocityY         垂直方向速度
     * @param consumed          子view是否fling了
     * @return true Behavior是否消耗了fling
     */
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout,
                                 V child,
                                 View target,
                                 float velocityX,
                                 float velocityY,
                                 boolean consumed) {
        return false;
    }

    /**
     * 在嵌套滑动的子View未fling之前告诉过来的准备fling的情况
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             该Behavior对应的View
     * @param target            具体嵌套滑动的那个子类
     * @param velocityX         水平方向速度
     * @param velocityY         垂直方向速度
     * @return true Behavior是否消耗了fling
     */
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
        return false;
    }

嵌套滑动引起的变化,根据前一篇文章Android 嵌套滑动分析的分析CoordinatorLayout 实现了 NestedScrollingParent,有嵌套滑动的时候会调用到CoordinatorLayout里面的onStartNestedScroll onNestedScrollAccepted onStopNestedScroll onNestedScroll onNestedPreScroll onNestedFling onNestedPreFling这些函数。至于是怎么调用到的这些函数可以看看Android 嵌套滑动分析的介绍。这些函数里面做的事情也都是大同小异的都是直接过度给了Behavior里面对应的函数。
这样我们就知道了Behavior的onStartNestedScroll + onNestedScrollAccepted + onStopNestedScroll + onNestedScroll + onNestedPreScroll + onNestedFling + onNestedPreFling。的调用时机是有嵌套滑动的时候会被调用到。

最后的最后给个简单的DEMO DEMO下载地址

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要实现这样的效果,需要自定义一个 Behavior 类。下面是一个简单的示例代码: ``` public class MyBehavior extends CoordinatorLayout.Behavior<View> { private int mTotalScrollRange; public MyBehavior() { super(); } public MyBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { return dependency instanceof AppBarLayout; } @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { if (dependency instanceof AppBarLayout) { mTotalScrollRange = ((AppBarLayout) dependency).getTotalScrollRange(); float ratio = -dependency.getY() / mTotalScrollRange; child.setTranslationY(ratio * child.getHeight()); return true; } return false; } } ``` 在这个 Behavior 类中,我们首先判断依赖的 view 是否是 AppBarLayout,如果是就设置一个 mTotalScrollRange 变量,该变量保存了 AppBarLayout 的总滑动范围。在 onDependentViewChanged 方法中,我们计算出当前的滑动比例 ratio,并根据这个比例来设置子 view 的 Y 轴偏移量,以实现子 view 的折叠和展开。 接下来,在布局文件中将该 Behavior 应用到 RecyclerView 对应的子 view 上即可: ``` <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior=".MyBehavior" /> ``` 注意,这个 Behavior 只是一个简单的示例,实际应用中可能需要根据具体需求进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值