天天看點

MD風格之豐富多變Toolbar

一. 下載下傳

二. 詳解

CoordinatorLayout繼承自ViewGroup,實作了NestedScrollingParent接口,可以說是超級版FrameLayout。

CoordinatorLayout

的用途主要有兩個:

  • 作為最頂層的application decor或者chrome layout.
  • 作為有特定互動的多個子View的容器.

可以通過給

CoordinatorLayout

的子View指定Behaviors讓子View之間産生不同的互動,也可是使用DefaultBehavior注解指定預設的互動行為。

Behaviors

可以實作各種各樣的互動和布局變化,如側滑抽屜和面闆的滑動隐藏、一個按鈕“粘”在其它元素上并跟随其一起滑動。

CoordinatorLayout

的子View可以有一個anchor錨,這個anchor的id必須是

CoordinatorLayout

的子View,但不要求一定是固定View或固定View的子View。使用

app:layout_anchor

app:layout_anchorGravity

屬性可以控制浮動View的相對位置。

簡單點說,通過

CoordinatorLayout

我們可以很好地控制容器中子View的互動行為,當然這離不開

CoordinatorLayout

的靜态抽象内部類

Behavior

的作用,

Behavior

作為

CoordinatorLayout

子View的Behavior互動插件,實作了一到多個子View之間的互動,如drags,、swipes、 flings等手勢。

CoordinatorLayout

常用于app bar(之前叫ActionBar,現在改用Toolbar,不過統稱AppBar)與其它View的互動。在

Toolbar

外面包裹一層

AppBarLayout

以便Toolbar和其它子View能更好地響應滾動事件。

AppBarLayout繼承自

LinearLayout

,是一個vertical的

LinearLayout

,并添加了

@CoordinatorLayout.DefaultBehavior(AppBarLayout.Behavior.class)

注解以響應滾動事件。其子View應該通過

setScrollFlags(int)

app:layout_scrollFlags

提供它們希望的滾動行為。

AppBarLayout

最好作為

CoordinatorLayout

的直接子View,否則它的很多方法将會失效。

AppBarLayout

需要一個可滾動的兄弟View以便知道何時滾動,這就需要為滾動View設定一個

AppBarLayout.ScrollingViewBehavior

執行個體(這裡将RecyclerView的layout_behavior屬性設定為

AppBarLayout.ScrollingViewBehavior

):

<?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:fitsSystemWindows="true"
    tools:context="com.frank.mdtest.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways" />

        <android.support.design.widget.TabLayout
            android:id="@+id/tl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|enterAlways" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>
           

這樣,當使用者滾動

RecyclerView

時,

AppBarLayout

就能響應滾動事件并根據其子View的

layout_scrollFlags

屬性控制其如何進入和退出螢幕,ScrollFlags包括:

  • scroll: 所有想滾出螢幕的View都要設定這個flag,如果不使用該flag則會固定在螢幕頂端。
  • exitUntilCollapsed: 該flag會使View在滾出螢幕前滾動直至“collapsed”狀态(View的最小高度)。
  • enterAlways: 該flag會確定任何向下的滾動都會導緻該View可見,通常用于“快速傳回”模式。
  • enterAlwaysCollapsed: 當View設定了minHeight并使用該flag時,View隻會在最小高度(“collapsed”)時進入,隻會在滾動View滾到頂的時候重新展開直至完整高度。
  • snap: 當滾動結束時,如果View隻是部分可見,将會被拉住并滾動至它的最近的邊緣。如:如果View僅僅底部25%可見,它将會被完全滾出螢幕。相反,如果它底部75%可見,它将滾動直至完全顯示。
注意:使用

layout_scrollFlags

的View必須在不适用該屬性的View之前定義,以確定這些View都能從上滾出螢幕,留下其後固定的View。

為了更詳細的控制

Toolbar

在“collapsing”過程中的元素互動,可以在

Toolbar

外面包裹一層

CollapsingToolbarLayout

CollapsingToolbarLayout繼承自

FrameLayout

,被設計為

AppBarLayout

的直接子View并作為

Toolbar

的容器,

CollapsingToolbarLayout

包含以下特征:

  • Collapsing title: 其Title會随布局完全展開可見而變大,随折疊而變小。可通過

    setTitle(CharSequence)

    方法設定Title,通過

    collapsedTextAppearance

    expandedTextAppearance

    屬性調整Title樣式。
  • Content scrim: 當滾動到”collapsed”門檻值時顯示或隐藏罩層。可通過

    setContentScrim(Drawable)

    方法設定罩層。
  • Status bar scrim: 當滾動到”collapsed”門檻值時在狀态欄顯示或隐藏罩層。可使通過

    setStatusBarScrim(Drawable)

    方法設定罩層并在LOLLIPOP裝置上起作用,同時需要

    android:fitsSystemWindows="true"

  • Parallax scrolling children: 其子View可以在該布局内做視差滾動,可以通過給子View(通常是Toolbar的兄弟ImageView)設定

    app:layout_collapseMode="parallax"

    app:layout_collapseParallaxMultiplier="0.7"

    實作。
  • Pinned position children: 其子View可以固定在指定位置,可以通過對Toolbar的

    app:layout_collapseMode="pin"

    屬性值設定確定滾動至被折疊後Toolbar能固定在螢幕頂端。

collapseMode包括:

  • off: 沒有任何scroll behavior的正常元素(預設屬性值)。
  • pin: View将會固定到

    CollapsingToolbarLayout

    的底端。
  • parallax: View将會随着

    CollapsingToolbarLayout

    做視差滾動。
<?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:fitsSystemWindows="true"
    tools:context="com.frank.mdtest.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />

        </android.support.design.widget.CollapsingToolbarLayout>

        <android.support.design.widget.TabLayout
            android:id="@+id/tl"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|snap" />

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>
           

通過

CollapsingToolbarLayout

app:layout_collapseMode="pin"

屬性值地設定確定滾動至被折疊後Toolbar能固定在螢幕頂端,更好的是,

Toolbar

配合

CollapsingToolbarLayout

使用時,Title的大小将随個展開和折疊調整自己的大小,不過在這種情況下需要調用

CollapsingToolbarLayout

而不是Toolbar的

setTitle()

方法設定Title。此外還可以通過給

CollapsingToolbarLayout

子View(通常是Toolbar的兄弟ImageView)設定

app:layout_collapseMode="parallax"

app:layout_collapseParallaxMultiplier="0.7"

屬性以實作視差滾動效果,然後給

CollapsingToolbarLayout

設定

app:contentScrim

屬性以便在折疊時設定罩層顔色:

<?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:fitsSystemWindows="true"
    tools:context="com.frank.mdtest.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl"
        android:layout_width="match_parent"
        android:layout_height="192dp"
        android:fitsSystemWindows="true" >

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/ctl"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="@color/colorPrimary"
            android:fitsSystemWindows="true" >

            <ImageView
                android:id="@+id/iv_toolbar_bg"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@drawable/skill_default_ow"
                android:fitsSystemWindows="true"
                app:layout_collapseMode="parallax"
                app:layout_scrollFlags="scroll|enterAlways" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:contentInsetStart="0dp" >

                <TextView
                    android:id="@+id/tv_title"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:gravity="center"
                    android:text="Title" />

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <ImageView
        android:id="@+id/fab"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/group_in"
        android:elevation="10dp"
        android:layout_marginLeft="20dp"
        app:layout_anchor="@id/abl"

</android.support.design.widget.CoordinatorLayout>
           

CoordinatorLayout.Behavior類是所有Behavior的基類,用來處理

CoordinatorLayout

的直接子View之間的動作互動。該抽象類已知的子類包括AppBarLayout.Behavior, AppBarLayout.ScrollingViewBehavior, BottomSheetBehavior, FloatingActionButton.Behavior, SwipeDismissBehavior。

CoordinatorLayout.Behavior

的主要方法包括:

  • public Behavior()

    .

    無參構造器,以便用new關鍵字執行個體化

  • public Behavior(Context context, AttributeSet attrs)

    .

    通過layout屬性或

    @CoordinatorLayout.DefaultBehavior

    注解間接執行個體化時,需要提供該構造器,以便

    CoordinatorLayout.LayoutParams

    可以通過反射執行個體化該Behavior
  • public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)

    .

    CoordinatorLayout

    向内分發觸摸事件之前進行處理。如果

    Behavior

    想要攔截并接管事件流,需要傳回true,預設傳回false。但如果

    Behavior

    攔截了觸摸事件,那剩下的事件流将發送到

    onTouchEvent

    方法
  • public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev)

    .

    Behavior

    攔截觸摸事件後接收和處理觸摸事件,如果

    Behavior

    處理了這次觸摸事件并還想繼續接收事件流的其它事件,需要傳回true,預設傳回false
  • public int getScrimColor(CoordinatorLayout parent, V child)

    .

    child view的罩層顔色,預設為

    Color.BLACK

  • public float getScrimOpacity(CoordinatorLayout parent, V child)

    .

    child view的罩層透明度,預設為

    0.f

  • public boolean blocksInteractionBelow(CoordinatorLayout parent, V child)

    .

    決定child view後面的view是否應該被阻塞,預設傳回

    getScrimOpacity(parent, child) > 0.f

  • public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency)

    .

    判斷給定的child view是否和其它某個兄弟view進行布局依賴。在請求layout時該方法至少要被調用一次,如果對于給定參數的(child,dependency)值對,該方法傳回true,那麼其父容器CoordinatorLayout将會:①總是在dependent view布局完成後才布局child view, 無論聲明順序如何②當dependency view的布局或位置變化時,會調用該方法。如果child view的布局依賴dependency view的布局則傳回true,預設傳回false

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

    .

    child view是否要對它所依賴的dependent view的改變做出響應。一旦dependent view在标準布局流中的大小和位置發生改變,該方法就會被調用。Behavior可以用該方法對child view進行更新操作,但如果Behavior通過該方法更改了child view的布局, 那它也應該在

    onLayoutChild(CoordinatorLayout, View, int)

    方法中做出相應的更改。由于所有子view都是按依賴順序進行布局的,是以在正常布局期間該方法将不會被調用。如果Behavior更改了child view的大小或位置, 應該傳回true,預設傳回false
  • public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency)

    .

    在dependent view被從父布局中移除後會調用該方法,此時

    Behavior

    可以對child view進行适當更新操作
  • public boolean isDirty(CoordinatorLayout parent, V child)

    .

    告訴

    CoordinatorLayout

    該child是否為dirty狀态,預設傳回false
  • public boolean onMeasureChild(CoordinatorLayout parent, V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

    .

    CoordinatorLayout

    要測量給定child view時調用。如果想要讓

    Behavior

    自己決定如何測量該child view,需要傳回true,預設傳回false(由

    CoordinatorLayout

    測量)
  • public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection)

    .

    CoordinatorLayout

    要布局給定child view時調用。如果

    Behavior

    實作了

    onDependentViewChanged(CoordinatorLayout, View, View)

    方法并更改了child view,那它也應實作該方法。如果

    Behavior

    對child view進行了布局則傳回true,預設傳回false
  • public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

    .

    CoordinatorLayout

    的一個子孫view嘗試初始化嵌套滾動時調用。

    CoordinatorLayout

    所有直接子view關聯的

    Behavior

    都可以響應該事件并傳回true以表明

    CoordinatorLayout

    作為該嵌套滾動的父容器,隻有該方法傳回了true的

    Behavior

    才能接收嵌套滾動的事件流,預設傳回false
  • public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes)

    .

    CoordinatorLayout

    接受嵌套滾動時會回調該方法
  • public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target)

    .

    當一次嵌套滾動結束時回調該方法

  • public void onNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed)

    .

    當嵌套滾動已經更新并且target已經或試圖滾動時調用,其中dxConsumed表示target作嵌套滾動過程中水準方向上消費的距離,dxUnconsumed表示使用者期望的水準方向上的滾動距離

  • public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed)

    .

    在嵌套滾動将要更新并且target消耗滾動距離之前調用,其中dx表示使用者想要滾動的水準像素值,consumed[0]表示消耗的dx距離,consumed[1]表示消耗的dy距離

  • public boolean onNestedFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY, boolean consumed)

    .

    當嵌套滾動子view開始或想要fling時調用,如果Behavior消費了該fling則需要傳回true,預設傳回false

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

    .

    當嵌套滾動子view将要fling時調用,如果Behavior消費了該fling則需要傳回true,預設傳回false

關于Nested Scrolling(嵌套滾動/嵌套滑動),想了解更多可參考NESTED SCROLLING WITH COORDINATORLAYOUT ON ANDROID及NestedScrolling事件機制源碼解析等blogs的簡單介紹,或閱讀相關源碼:
  • NestedScrollingParent
  • NestedScrollingParentHelper
  • NestedScrollingChild
  • NestedScrollingChildHelper

References

Android Developers Blog

Android Open Source Project

CodePath Android Cliffnotes