完全搞懂CoordinatorLayout Behavior 你能做些什么
完全搞懂CoordinatorLayout Behavior 系列之API讲解
完全搞懂CoordinatorLayout Behavior之源码学习
完全搞懂CoordinatorLayout Behavior之实战一
MaterialDesign
相信大家一定很熟悉,Toolbar
、CardView
、AppbarLayout
、TabLayout
等等很多控件大家或多或少都应用过,但是由于国内的审美,所以这些控件并不想国外那么普遍。 而对于今天介绍的CoordinatorLayout
和Behavior
也是属于MaterialDesign
的一部分。相比前面的那些控件单纯的只是使用api 属性就能达到效果而CoordinatorLayout
和Behavior
的学习难度和使用上相对较高。 但是学习和使用这个,在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 paren
t: 协调者
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的介绍。
谢谢你的阅读,如果对本篇文章有质疑或者不懂的可以通过评论或者私信我。