系列文章:
CoordinatorLayout系列(一):Behavior
CoordinatorLayout系列(二)AppBarLayout
CoordinatorLayout系列(三)AppBarLayout之layout_scrollFlags
CoordinatorLayout系列(四)CollapsingToolbarLayout
CoordinatorLayout系列(五)例子
在上一篇文章裏面CoordinatorLayout系列(二)AppBarLayout裏面講到了AppBarLayout的使用,這一篇準備講一下AppBarLayout是怎麼通過layout_scrollFlags控制child view的滑動,以及各種flag產生的效果。
一、Flags種類
Flags在代碼裏面具體位於AppBarLayout的LayoutParams中:
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_Layout);
scrollFlags = a.getInt(R.styleable.AppBarLayout_Layout_layout_scrollFlags, 0);
if (a.hasValue(R.styleable.AppBarLayout_Layout_layout_scrollInterpolator)) {
int resId = a.getResourceId(R.styleable.AppBarLayout_Layout_layout_scrollInterpolator, 0);
scrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator(c, resId);
}
a.recycle();
}
Flags分爲SCROLL_FLAG_SCROLL
、SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
、SCROLL_FLAG_ENTER_ALWAYS
、SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED
、SCROLL_FLAG_SNAP
、SCROLL_FLAG_SNAP_MARGINS
這麼六種,這裏只介紹前四種
二、Flags的效果
-
scroll
所有需要滑動的效果,都要跟上這個flag,否則Toolbar根本不能滑動。 -
scroll | enterAlways
這個效果很常見了,就是ToolBar跟隨scrollview進行滑入滑出
-
scroll|enterAlways|enterAlwaysCollapsed
這個效果要設置好ToolBar的minHeight和真正的Height
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"
android:layout_height="200dp"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
app:title="AppBarLayoutExample"
app:titleTextAppearance="@style/ToolbarTitleStyle"></androidx.appcompat.widget.Toolbar>
滑動的機制是,當向上滑動時,和只設置enterAlwalys一樣,當向下滑動時,先滑動minHeight這麼多,剩下的向下滑動事件由scrollView消費,等到scrollview滑到頂部時,再向下滑動,這時ToolBar才滑動剩餘的部分。
- scroll|exitUntilCollapsed
這組標誌位也要設置minHeight
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:minHeight="20dp"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:title="AppBarLayoutExample"
app:titleTextAppearance="@style/ToolbarTitleStyle"></androidx.appcompat.widget.Toolbar>
滑動的機制是向上滑動時,不會全部滑出,會minHeight這麼多,當向下滑動時,要等到scrollview滑到頂部,才往下滑入。
三、滑動的具體實現
看到上面幾種滑動,那麼我們就要思考到,CoordinatorLayout是如何實現上面的效果呢?
其實具體的實現效果還是比較複雜的,我在這裏只是講一下大概的實現方式,要全部講清楚還要深入到view的內部細節才能完全講清。
在上一篇中講到,CoordinatorLayout和Recyclerview是基於NestedScroll機制實現聯動效果的,Move事件的傳遞順序是這樣的:
1、首先,點擊事件傳遞到Recyclerview,進入onTouchEvent方法
2、由於NestedScroll機制,需要先詢問parent,是否消費事件,進入CoordinatorLayout.onNestedPreScroll,這裏面就會由childview的Behavior代理消費事件,進入到AppBarLayout.BaseBehavior.onNestedPreScroll
3、parent消費完一部分之後,就由Recyclerview自己消費了,如果滑到頂部的話,那麼肯定還有一部分沒消費完,因此還要parent消費剩下的,進入到CoordinatorLayout.onNestedScroll
4、和2相同,仍然由AppBarLayout.BaseBehavior.onNestedScroll代理消費
上面就是整個的事件消費流程,2、4兩點是很重要的,因爲涉及到AppBarLayout控制child view的滑動行爲,以onNestedPreScroll爲例,這個行爲代表着當我們準備讓Recyclerview滑動時,最終是被AppBarLayout消費掉了,典型的enterAlwayls這個flag的行爲
public void onNestedPreScroll(
CoordinatorLayout coordinatorLayout,
@NonNull T child,
View target,
int dx,
int dy,
int[] consumed,
int type) {
if (dy != 0) {
int min;
int max;
if (dy < 0) {
// We're scrolling down
//計算AppBarLayout整個的可以滑動的高度,minHeight是不參與滑動距離的
min = -child.getTotalScrollRange();
//getDownNestedPreScrollRange會根據這個階段計算出最大可以滑動的距離
max = min + child.getDownNestedPreScrollRange();
} else {
// We're scrolling up
min = -child.getUpNestedPreScrollRange();
max = 0;
}
if (min != max) {
//當可以滑動的最小距離不等於最大距離時,說明此時AppBarLayout是可以滑動的,因此就讓AppBarLayout消費掉dy事件的一部分
consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
}
}
if (child.isLiftOnScroll()) {
child.setLiftedState(child.shouldLift(target));
}
}
上面最重要的點就是min和max的計算方法,min就是最小可以滑動的距離,max就是最大可以滑動的距離,這兩個值其實是定值,和child view的高度,flags相關,計算的過程就是和flags相關的,因此不同的flags就能產生不同的效果。