完全搞懂CoordinatorLayout Behavior 系列之API讲解

完全搞懂CoordinatorLayout Behavior 你能做些什么
完全搞懂CoordinatorLayout Behavior 系列之API讲解
完全搞懂CoordinatorLayout Behavior之源码学习
完全搞懂CoordinatorLayout Behavior之实战一

MaterialDesign相信大家一定很熟悉,ToolbarCardViewAppbarLayoutTabLayout 等等很多控件大家或多或少都应用过,但是由于国内的审美,所以这些控件并不想国外那么普遍。 而对于今天介绍的CoordinatorLayoutBehavior 也是属于MaterialDesign的一部分。相比前面的那些控件单纯的只是使用api 属性就能达到效果而CoordinatorLayoutBehavior的学习难度和使用上相对较高。 但是学习和使用这个,在app效果和自身收获上也是非常大的。
首先看看关于CoordinatorLayout 的介绍 Coordinator(协调者)
CoordinatorLayout源码链接
CoordinatorLayout 其实就是一个超级FrameLayout ,但是它并不是继承自FrameLayout。它一般作为一个页面的根布局,另一个重要作用就是作为子视图相互交互的一个协调容器。 换句话说 子视图相互动画必须靠它来进行事件的分派协调。
那么Behavior是做什么的呢?Behavior(行为)主要指定在参与CoordinatorLayout分配协调的动作的子视图上。在Behavior定义设定好的视图的动作(动画)交互,然后交给CoordinatorLayout调用分配。
所以我的重点主要放在Behavior身上,只有很好了解了Behavior相关的API我们就能写出各种炫酷的动画效果。

一、 boolean layoutDependsOn(CoordinatorLayout parent, V child,View dependency)

/**
 * Determine whether the supplied child view has another specific sibling view as a
 * layout dependency.
 *
 * <p>This method will be called at least once in response to a layout request. If it
 * returns true for a given child and dependency view pair, the parent CoordinatorLayout
 * will:</p>
 * <ol>
 *     <li>Always lay out this child after the dependent child is laid out, regardless
 *     of child order.</li>
 *     <li>Call {@link #onDependentViewChanged} when the dependency view's layout or
 *     position changes.</li>
 * </ol>
 *
 * @param parent the parent view of the given child
 * @param child the child view to test
 * @param dependency the proposed dependency of child
 * @return true if child's layout depends on the proposed dependency's layout,
 *         false otherwise
 *
 * @see #onDependentViewChanged(CoordinatorLayout, View, View)
 */
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child,
        @NonNull View dependency) {
    return false;
}

给Behavior作用的子视图指定一个同层级的视图作为依赖视图。如果指定了被依赖的视图并返回true,不管子视图的顺序是怎样的,只要指定依赖的视图发生了改变,那么behavior作用的视图也会得到改变,当然这个改变是有用户自己定义的。
参数详解:
CoordinatorLayout parent: 协调者
V child:behavior 作用的视图(你的behavior写在谁身上这个view就是谁)
View dependency:被依赖视图
为了让大家更加清晰的理解,举例说明下:

//public class WeatherBehavior extends CoordinatorLayout.Behavior 
@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {

    if (dependency instanceof NestedScrollView){
        return true;
    }
    return false;
}

//布局
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/img_header"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        app:layout_behavior=".ImageHeaderBehavior"
        android:background="@mipmap/home_top_bg"/>
    <android.support.v4.widget.NestedScrollView
        android:id="@+id/scroll_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="@color/orange"/>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="@color/aqua"/>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

上面的例子中 NestedScrollView就是被依赖View (dependency) 参数 、ImageView 就是child参数。

二、public boolean onDependentViewChanged( CoordinatorLayout parent, V child, View dependency)

/**
* Respond to a change in a child's dependent view
*
* <p>This method is called whenever a dependent view changes in size or position outside
* of the standard layout flow. A Behavior may use this method to appropriately update
* the child view in response.</p>
*
* <p>A view's dependency is determined by
* {@link #layoutDependsOn(CoordinatorLayout, View, View)} or
* if {@code child} has set another view as it's anchor.</p>
*
* <p>Note that if a Behavior changes the layout of a child via this method, it should
* also be able to reconstruct the correct position in
* {@link #onLayoutChild(CoordinatorLayout, View, int) onLayoutChild}.
* <code>onDependentViewChanged</code> will not be called during normal layout since
* the layout of each child view will always happen in dependency order.</p>
翻译: 请注意,如果行为通过此方法更改子级的布局,它还应该能够在{@link#onLayoutChild(CoordinatorLayout,View,int)onLayoutChild}中重建正确的位置。
<code>onDependentViewChanged</code>在正常布局期间将不会调用,因为每个子视图的布局将始终按依赖顺序发生。
*
* <p>If the Behavior changes the child view's size or position, it should return true.
* The default implementation returns false.</p>
*
* @param parent the parent view of the given child
* @param child the child view to manipulate
* @param dependency the dependent view that changed
* @return true if the Behavior changed the child view's size or position, false otherwise
*/
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child,
       @NonNull View dependency)

上面一个方法有提到,当被依赖布局的大小和位置发生改变时就会调用视图的behavior的这个方法坐对应的改变。如果在这个方法改变了child的位置和大小就应该返回true。

三、public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)

/**
 * Called when the parent CoordinatorLayout is about the lay out the given child view.
 *
 * <p>This method can be used to perform custom or modified layout of a child view
 * in place of the default child layout behavior. The Behavior's implementation can
 * delegate to the standard CoordinatorLayout measurement behavior by calling
 * {@link CoordinatorLayout#onLayoutChild(View, int)
 * parent.onLayoutChild}.</p>
 *
 * <p>If a Behavior implements
 * {@link #onDependentViewChanged(CoordinatorLayout, View, View)}
 * to change the position of a view in response to a dependent view changing, it
 * should also implement <code>onLayoutChild</code> in such a way that respects those
 * dependent views. <code>onLayoutChild</code> will always be called for a dependent view
 * <em>after</em> its dependency has been laid out.</p>
 *
 * @param parent the parent CoordinatorLayout
 * @param child child view to lay out
 * @param layoutDirection the resolved layout direction for the CoordinatorLayout, such as
 *                        {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
 *                        {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
 * @return true if the Behavior performed layout of the child view, false to request
 *         default layout behavior
 */
public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child,
        int layoutDirection)

当父布局(CoordinatorLayout)初始化给子视图布局的时候,如果子视图指定的behavior就会被调用。
我通常在这个方法坐child 视图的初始化布局。

四、public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int axes, int type)

/**
 * Called when a descendant of the CoordinatorLayout attempts to initiate a nested scroll.
 *
 * <p>Any Behavior associated with any direct child of the CoordinatorLayout may respond
 * to this event and return true to indicate that the CoordinatorLayout should act as
 * a nested scrolling parent for this scroll. Only Behaviors that return true from
 * this method will receive subsequent nested scroll events.</p>
 *
 * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
 *                          associated with
 * @param child the child view of the CoordinatorLayout this Behavior is associated with
 * @param directTargetChild the child view of the CoordinatorLayout that either is or
 *                          contains the target of the nested scroll operation
 * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
 * @param axes the axes that this nested scroll applies to. See
 *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
 *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
 * @param type the type of input which cause this scroll event
 * @return true if the Behavior wishes to accept this nested scroll
 *
 * @see NestedScrollingParent2#onStartNestedScroll(View, View, int, int)
 */
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int axes, int type)

当CoordinatorLayout的子视图尝试启动嵌套滑动时被调用,当返回true时,CoordinatorLayout将充当nested scrolling parent来处理这次滚动。
注意只有return true 这个behavior才会收到后面的所有嵌套滑动的事件回调。
简单讲这个方法就是控制这个child视图是否能得到嵌套滑动的事件回调,如果返回false,那么CoordinatorLayout在收到滑动控件的滚动事件后,不会传递这个这个视图的behavior,用于用户坐一些动作。
参数详解
directTargetChild : the child view of the CoordinatorLayout that either is or contains the target of the nested scroll operation
axes:滚动方向

五、public void onNestedPreScroll(CoordinatorLayout coordinatorLayout,V child, View target, int dx, int dy, int[] consumed, @NestedScrollType int type)

/**
 * Called when a nested scroll in progress is about to update, before the target has
 * consumed any of the scrolled distance.
 *
 * <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
 * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
 * that returned true will receive subsequent nested scroll events for that nested scroll.
 * </p>
 *
 * <p><code>onNestedPreScroll</code> is called each time the nested scroll is updated
 * by the nested scrolling child, before the nested scrolling child has consumed the scroll
 * distance itself. <em>Each Behavior responding to the nested scroll will receive the
 * same values.</em> The CoordinatorLayout will report as consumed the maximum number
 * of pixels in either direction that any Behavior responding to the nested scroll reported
 * as consumed.</p>
 *
 * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
 *                          associated with
 * @param child the child view of the CoordinatorLayout this Behavior is associated with
 * @param target the descendant view of the CoordinatorLayout performing the nested scroll
 * @param dx the raw horizontal number of pixels that the user attempted to scroll
 * @param dy the raw vertical number of pixels that the user attempted to scroll
 * @param consumed out parameter. consumed[0] should be set to the distance of dx that
 *                 was consumed, consumed[1] should be set to the distance of dy that
 *                 was consumed
 * @param type the type of input which cause this scroll event
 *
 * @see NestedScrollingParent2#onNestedPreScroll(View, int, int, int[], int)
 */
public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
        @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
        @NestedScrollType int type) {
    if (type == ViewCompat.TYPE_TOUCH) {
        onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
    }
}

当嵌套滚动的距离消耗之前被调用,在嵌套滚动child消耗自己的滚动距离之前调用。
参数详解
dx dy:xy轴滑动的距离, 通常只需要处理一个
consumed:这是个重要的参数consumed,可以修改这个数组表示你消费了多少距离。假设用户滑动了100px,child 做了90px 的位移,你需要把 consumed[1]的值改成90,这样coordinatorLayout就能知道只处理剩下的10px的滚动。

六、public void onNestedScroll( CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type)

/**
* Called when a nested scroll in progress has updated and the target has scrolled or
* attempted to scroll.
*
* <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
* to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
* that returned true will receive subsequent nested scroll events for that nested scroll.
* </p>
*
* <p><code>onNestedScroll</code> is called each time the nested scroll is updated by the
* nested scrolling child, with both consumed and unconsumed components of the scroll
* supplied in pixels. <em>Each Behavior responding to the nested scroll will receive the
* same values.</em>
* </p>
*
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
*                          associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param target the descendant view of the CoordinatorLayout performing the nested scroll
* @param dxConsumed horizontal pixels consumed by the target's own scrolling operation
* @param dyConsumed vertical pixels consumed by the target's own scrolling operation
* @param dxUnconsumed horizontal pixels not consumed by the target's own scrolling
*                     operation, but requested by the user
* @param dyUnconsumed vertical pixels not consumed by the target's own scrolling operation,
*                     but requested by the user
* @param type the type of input which cause this scroll event
*
* @see NestedScrollingParent2#onNestedScroll(View, int, int, int, int, int)
*/
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
       @NonNull View target, int dxConsumed, int dyConsumed,
       int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type)

当嵌套滚动发生后调用。这是我们后面操作滚动的核心方法,嵌套滚动的控件每次滑动的距离值都会通过CoordinatorLayout这个协调者传递给behavior的这个方法接收。
参数详解
dxConsumed 、dxConsumed:x y轴滚动消耗的距离
dxUnconsumed 、dyUnconsumed:x y轴上未消耗的距离

七、public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY)

/**
* Called when a nested scrolling child is about to start a fling.
*
* <p>Any Behavior associated with the direct child of the CoordinatorLayout may elect
* to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
* that returned true will receive subsequent nested scroll events for that nested scroll.
* </p>
*
* <p><code>onNestedPreFling</code> is called when the current nested scrolling child view
* detects the proper conditions for a fling, but it has not acted on it yet. A
* Behavior can return true to indicate that it consumed the fling. If at least one
* Behavior returns true, the fling should not be acted upon by the child.</p>
*
* @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
*                          associated with
* @param child the child view of the CoordinatorLayout this Behavior is associated with
* @param target the descendant view of the CoordinatorLayout performing the nested scroll
* @param velocityX horizontal velocity of the attempted fling
* @param velocityY vertical velocity of the attempted fling
* @return true if the Behavior consumed the fling
*
* @see NestedScrollingParent#onNestedPreFling(View, float, float)
*/
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
       @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
   return false;
}

Fling看着这个单词是不是很熟悉,抛、射一个动作,代表的就是动作脱手以后的动作。所以这个方法就是在当手离开屏幕以后,滑动由于动能而产生的惯性动作。return 表示它消耗了Fling动作。

八、public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target,@ScrollAxis int axes, @NestedScrollType int type)

/**
 * Called when a nested scroll has been accepted by the CoordinatorLayout.
 *
 * <p>Any Behavior associated with any direct child of the CoordinatorLayout may elect
 * to accept the nested scroll as part of {@link #onStartNestedScroll}. Each Behavior
 * that returned true will receive subsequent nested scroll events for that nested scroll.
 * </p>
 *
 * @param coordinatorLayout the CoordinatorLayout parent of the view this Behavior is
 *                          associated with
 * @param child the child view of the CoordinatorLayout this Behavior is associated with
 * @param directTargetChild the child view of the CoordinatorLayout that either is or
 *                          contains the target of the nested scroll operation
 * @param target the descendant view of the CoordinatorLayout initiating the nested scroll
 * @param axes the axes that this nested scroll applies to. See
 *                         {@link ViewCompat#SCROLL_AXIS_HORIZONTAL},
 *                         {@link ViewCompat#SCROLL_AXIS_VERTICAL}
 * @param type the type of input which cause this scroll event
 *
 * @see NestedScrollingParent2#onNestedScrollAccepted(View, View, int, int)
 */
public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
        @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
        @ScrollAxis int axes, @NestedScrollType int type)

当onStartNestedScroll return true以后,表示当前视图接受嵌套滚动后调用,通常在这个方法坐一些初始化动作。
总结
CoordinatorLayout是子视图的协调者,子视图通过layoutDependsOn指定自己需要监视的视图, 当被监视的视图发生改变的时候,该视图就可以做相应的改变。而这个改变的动作并不有CoordinatorLayout 这个协调者来执行,它通过一个内部类来定义了一套规则方法,需要被协调的子视图通过重写对应方法,定义自己想要的动作来完成协调执行。这里面有个观察者 和被观察者的思想在里面。behavior标记的视图是观察者,然后layoutDependsOn返回true的那个视图就是被观察者。那么观察者 和被观察者是怎么进行通讯呢?想要继续了解请看下一篇关于CoordinatorLayout Behavior的介绍。
谢谢你的阅读,如果对本篇文章有质疑或者不懂的可以通过评论或者私信我。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CoordinatorLayout 是 Android Design Support Library 提供的一个特殊的布局容器,它可以协调其内部的子 View 之间的交互行为,实现各种复杂的交互效果,比如 AppBarLayout 和 FloatingActionButton 的联动效果。 CoordinatorLayout 的主要作用是让子 View 之间可以通过 Behavior 进行交互。Behavior 是指子 View 在 CoordinatorLayout 中的交互行为的定义,可以让子 View 之间实现联动效果,如 FloatingActionButton 随着 Snackbar 的出现和消失而改变位置,子 View 之间的交互行为通过 Behavior 实现。 使用 CoordinatorLayout 需要注意以下几点: 1. CoordinatorLayout 必须作为根布局。 2. 子 View 需要设置 app:layout_behavior 属性,指定其交互行为的 Behavior。 3. 子 View 的交互行为需要在 Behavior 中定义。 下面是一个简单的 CoordinatorLayout 的示例: ``` <androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="match_parent" android:layout_height="match_parent"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <com.google.android.material.appbar.CollapsingToolbarLayout android:layout_width="match_parent" android:layout_height="200dp" app:layout_scrollFlags="scroll|exitUntilCollapsed"> <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop" android:src="@drawable/image"/> <androidx.appcompat.widget.Toolbar android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="pin"/> </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior"/> </androidx.coordinatorlayout.widget.CoordinatorLayout> ``` 在上面的示例中,AppBarLayout 和 CollapsingToolbarLayout 实现了一个可折叠的 Toolbar,RecyclerView 使用了 appbar_scrolling_view_behavior Behavior,实现了和可折叠的 Toolbar 的联动效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值