『Material Design 入門學習筆記』CollapsingToolbarLayout 與 AppBarLayout(附 demo)

mymdeep發表於2017-04-19

這篇文章中,介紹到的其它元件有TextInputLayout,FloatingActionButton,Snackbar,CoordinatorLayout。
該專題的其它文章:
『Material Design入門學習筆記』前言
『Material Design入門學習筆記』動畫(含demo)
『Material Design 入門學習筆記』主題與 AppCompatActivity(附 demo)
『Material Design入門學習筆記』RecyclerView與CardView(附demo)
demo下載

元件介紹

CoordinatorLayout

這一篇講的元件有些多,所以要從頭說起,頭是哪呢?就是佈局檔案,佈局檔案的頭是哪呢?就是CoordinatorLayout。
CoordinatorLayout被譽為超級FrameLayout。
主要是實現兩個功能:

  • 作為頂層佈局
  • 排程協調子佈局
    比如說,接下來會講到的Snackbar,就只有依靠CoordinatorLayout才能實現一些特殊操作,這個後面會講到。
    CoordinatorLayout功能強大,主要是依靠一個Behavior物件。CoordinatorLayout自己並不控制View,所有的控制權都在Behavior。
    這個Behavior可以通過自定義的方式來實現自己的邏輯。寫到這裡,我突然發現後面沒法介紹了,因為介紹Behavior的功能需要依賴其它元件,那就放在後面再介紹Behavior吧。
    這裡強調一下,接下來介紹的所有元件,都會用到這個庫:
    compile 'com.android.support:design:22.2.1'複製程式碼

    AppBarLayout

    AppBarLayout繼承自LinearLayout,佈局方向為垂直方向。但是它內部封裝了一些手勢變換的動畫。
    首先它需要依賴CoordinatorLayout作為父容器,同時也要求一個具有可以獨立滾動的子View。
    他的子View會有一個設定引數 app:layout_scrollFlags有一下幾種設定:
  • scroll:設為scroll的View會跟隨滾動事件一起發生移動, 所有想滾動出螢幕的view都需要設定這個flag,沒有設定這個flag的view將被固定在螢幕頂部。
  • enterAlways:設為enterAlways的View,當ScrollView往下滾動時,該View會直接往下滾動,而不用考慮ScrollView是否在滾動,這個flag讓任意向下的滾動都會導致該view變為可見,如啟用快速“返回模式”。
  • exitUntilCollapsed:值設為exitUntilCollapsed的View,當這個View要往上逐漸“消逝”時,會一直往上滑動,直到剩下的的高度達到它的最小高度後,再響應ScrollView的內部滑動事件。簡而言之,滾動退出螢幕,最後摺疊在頂端。
  • enterAlwaysCollapsed:是enterAlways的附加選項,一般跟enterAlways一起使用,它是指,View在往下“出現”的時候,首先是enterAlways效果,當View的高度達到最小高度時(注意你的view需要設定minHeight屬性),View就暫時不去往下滾動,直到ScrollView滑動到頂部不再滑動時,View再繼續往下滑動,直到滑到View的頂部結束。
    需要注意的是,後面兩種模式基本只有在CollapsingToolbarLayout才有用。

    CollapsingToolbarLayout

    CollapsingToolbarLayout繼承自FrameLayout,作用是為Toolbar提供了摺疊功能。
    下面介紹一些引數:
    app:contentScrim這個引數可以讓CollapsingToolbarLayout在收縮的時候,背景圖片消失的時候,指定一個顏色。
    app:collapsedTitleGravity 指定摺疊狀態的標題如何放置,可選值:top、bottom等
    app:collapsedTitleTextAppearance指定摺疊狀態標題文字的樣貌
    app:expandedTitleTextAppearance指定展開狀態標題文字的樣貌
    app:expandedTitleGravity 展開狀態的標題如何放置
    app:titleEnabled指定是否顯示標題文字
    app:toolbarId指定與之關聯的ToolBar,如果未指定則預設使用第一個被發現的ToolBar子View
    app:expandedTitleMarginStart指定展開狀態標題距離開始位置的高度
    app:expandedTitleMarginBottom指定展開狀態標題距離底部的距離
    app:expandedTitleMarginEnd指定展開狀態標題距離結束位置的高度
    app:layout_collapseParallaxMultiplier="0.7"設定視差的係數,介於0.0-1.0之間。
    app:layout_collapseMode“pin”:固定模式,在摺疊的時候最後固定在頂端;“parallax”:視差模式,在摺疊的時候會有個視差摺疊的效果。
    app:layout_anchor``app:layout_anchorGravity兩個屬性連同一起,與某一個AppBarLayout控制元件相關聯,確定位置。

    TextInputLayout

    TextInputLayout控制元件和LinearLayout完全一樣,它只是一個容器。跟ScrollView一樣,TextInputLayout只接受一個子元素。子元素需要是一個EditText元素。
    EditText中有一個引數hint,這個大家都不陌生,但是如果EditText放在TextInputLayout中,則會讓hint變成一個在EditText上方的浮動標籤,同時還包括一個material動畫。

    FloatingActionButton

    FloatingActionButton是一個圓形的按鈕,跟Button一樣會有點選事件。不同的是,可以設定陰影,可以設定反饋動畫。而且如果你設定的背景圖片是一個方形的,會在圓形按鈕中間顯示。

    佈局檔案

    上面介紹了一些基本屬性,現在看一下整體的佈局檔案
<?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"
                                                 android:id="@+id/main_content"
                                                 android:layout_width="match_parent"
                                                 android:layout_height="match_parent"
                                                 android:fitsSystemWindows="true">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="256dp"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        android:fitsSystemWindows="true">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:expandedTitleMarginStart="48dp"
            app:expandedTitleMarginEnd="64dp">

            <ImageView
                android:id="@+id/backdrop"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:fitsSystemWindows="true"
                android:src="@drawable/logo"
                app:layout_collapseMode="parallax"
                />

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

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

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



    <android.support.design.widget.TextInputLayout
        android:id="@+id/inputwrapper"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <EditText
            android:id="@+id/input"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="輸入內容"/>

    </android.support.design.widget.TextInputLayout>
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|right|end"
        android:src="@mipmap/ic_launcher"
        android:id="@+id/btn_ok"
        />

</android.support.design.widget.CoordinatorLayout>複製程式碼

在該佈局檔案中使用了Toolbar,關於它的相關屬性,可以參考我之前的文章,這裡不再重複介紹。
TextInputLayout有人會問為什麼TextInputLayout使用app:layout_behavior="@string/appbar_scrolling_view_behavior",其實為了適配好位置,最好在外面加一層NestedScrollView,因為涉及到滑動嘛,但是這裡暫時不用,因為那是下一篇文章的內容。

程式碼

下面貼出相關的程式碼:

public class CollapsingActivity extends AppCompatActivity {
    private FloatingActionButton okBtn;
    private EditText editText;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_collapsing);
        initToolBar();
        TextInputLayout inputWrapper = (TextInputLayout) findViewById(R.id.inputwrapper);
        editText = (EditText)findViewById(R.id.input);
        okBtn = (FloatingActionButton)findViewById(R.id.btn_ok);
        okBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                hideKeyboard();
                Snackbar mySnackbar = Snackbar.make(findViewById(R.id.main_content),editText.getText().toString(),Snackbar.LENGTH_SHORT);
                mySnackbar.setAction(editText.getText().toString(), new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Toast.makeText(CollapsingActivity.this,"click Snackbar",Toast.LENGTH_LONG).show();
                    }
                });
                mySnackbar.show();

            }
        });
        okBtn.setCompatElevation(0);
        inputWrapper.setHintAnimationEnabled(true);
        inputWrapper.setHint("請輸入內容");
    }
    private void hideKeyboard() {
        View view = getCurrentFocus();
        if (view != null) {
            ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).
                hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
        }
    }
    private void initToolBar(){
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        setTitle("CollapsingActivity");//設定標題
        toolbar.setNavigationIcon(R.mipmap.ic_launcher_round);//設定返回鍵,我這裡沒有,就有icon代替吧
        toolbar.setNavigationOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });//返回監聽
        toolbar.setSubtitle("by deep");//設定副標題
    }
}複製程式碼

簡單說一下程式碼的邏輯,在介面上有個輸入框,輸入內容,點選FloatingActionButton,會將內容做一個Snackbar的展示,這是在底部會有一個Snackbar,Snackbar可以通過setAction方法設定點選事件,它的第一個引數是按鈕顯示的文字,第二個引數是點選事件,我這裡是點選Snackbar會再彈出一個Toast。
關於Snackbar的初始化,可以使用make方法,這裡需要注意它的引數:

  • 第一個引數是一個view,我試過,可以設定介面任何一個View,但是最好設定為父容器CoordinatorLayout,這樣的話,會有一個滑動消除的手勢。
  • 第二個引數是顯示的內容,第三個引數是顯示時長。

    自定義behavior

    在上文中提到過CoordinatorLayout功能如此強大,全依賴於Behavior物件。
    我們先看一下原始碼,Behavior是一個抽象類,public static abstract class Behavior<V extends View>,我們可以根據View隨意去實現。
    原始碼中的實現這個抽象類的有以下幾個:
  1. AppBarLayout.Behavior;
  2. AppBarLayout.ScrollingViewBehavior;
  3. FloatingActionButton.Behavior;
  4. Snackbar.Behavior;
  5. BottomSheetBehaviro;
  6. SwipeDismissBehavior;
  7. HeaderBehavior;
  8. ViewOffsetBehavior;
  9. HeaderScrollingViewBehavior;
    其中也有抽象類。
    上面我們用到的就有ScrollingViewBehavior和FloatingActionButton.Behavior,由於FloatingActionButton.Behavior預設就是設定好的,所以我們沒有在佈局檔案中寫。
    那麼我們現在可以重新寫一個FloatingActionButton的Behavior。
    在寫之前最好去讀一下Behavior這個抽象類,其中很多概念和關聯需要弄明白。
    其中重要的兩個概念
  • child 它是一個View, 是該Behavior的關聯物件,也即Behavior所要操作的物件
  • dependency ,也是個View,是 child的依賴物件,同時也是Behavior對child進行操作的根據
    例如我們上面那個佈局檔案中child就是FloatingActionButton,dependency就是AppBarLayout。
    還有一些方法:
    layoutDependsOn用來確定依賴關係,原文是:

    Determine whether the supplied child view has another specific sibling view as a layout dependency.
    意思是,這個view有沒有兄弟view。

onDependentViewChanged

Respond to a change in a child's dependent view
當我們的 dependency 發生改變的時候,這個方法會呼叫,而我們在 onDependentViewChanged 方法里根據需求做相應的介面處理即可。
我們簡單寫一個FloatingActionButton跟隨AppBarLayout移動的例子:
```
public class CustomBehavior extends CoordinatorLayout.Behavior {

public CustomBehavior() {
}

public CustomBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
}

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
   return dependency instanceof AppBarLayout;
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child, View dependency) {
    float translationY = Math.abs(dependency.getTop());
    Log.e("xxxxxx ","translationY="+translationY);
    child.setY(800-translationY);
    return true;
}複製程式碼

}

然後設定到佈局檔案的對應位置:複製程式碼


```
自定義Behavior的方式就介紹到這。

總結

本來想在文章中插圖,但是由於動圖過大,上傳總是失敗,所以,還是請感興趣的朋友執行demo看一下效果即可。後面的文章還是對本文的延伸,增添了一些新的元件,敬請關注。
有問題可以給我留言,或者關注我的公眾號留言。

『Material Design 入門學習筆記』CollapsingToolbarLayout 與 AppBarLayout(附 demo)

相關文章