Material Design 系列之 CardView、FAB 和 Snackbar

前言

更多Material Design 文章請看:
Material Design 之 Toolbar 開發實踐總結
Material Design之 AppbarLayout 開發實踐總結
Material Design 之 Behavior的使用和自定義Behavior
Material Design 之 TabLayout 使用
Material Design 之 TextInputLayout和TextInputEditText
這是Material Design 系列的最後一篇文章,前面幾篇文章講了Material Design中一些比較重要而且經常使用的控件,最後這一篇文章算是一個補充,講一下CardView、FloatActionButton 和 Snackbar。因爲用法比較簡單,因此就不每個都拎出來單講。如下分別是這三個控件的用法。html

CardView(卡片)

卡片是一張帶有材料屬性的紙片,用做展現更多詳細信息的入口點。卡片包含了一組特定的數據集,數據集含有各類相關信息,如主題照片、文本,連接等等。卡片有固定的寬度和可變的高度。最大高度限制於可適應平臺上單一視圖的內容,但若是須要它能夠臨時擴展(例如,顯示評論欄)。卡片不會翻轉以展現其背後的信息。java

卡片集是共面的,或者統一平面的多張卡片佈局。以下:android

card_collection_1.png

一張卡片包含了一組特定的數據集,考慮在如下這些狀況使用卡片:git

  • 做爲一個集合,比較多種數據類型,好比:圖片、視頻和文本。github

  • 不須要直接比較(如:用戶不會直接比較圖片和文本)app

  • 支持內容高度可變,好比評論。ide

  • 包含響應按鈕,好比+1 按鈕或者評論oop

  • 要使用網格列表,但須要顯示更多文原本補充圖片佈局

以上就是使用卡片的一些場景,看一個使用不當的例子(圖片來自官網):ui

card_wrong.png

錯誤示例: 這種卡片的使用分散了用戶的注意力,不能快速瀏覽,也不能忽略掉,因此將這些內容放在不一樣的卡片上是難以理解的。其實國內有些知名APP也沒有按照規範來作,給咱們作了錯誤的示範,如知乎日報首頁:

zhihuribao.png

正確的用法以下:

card_right.png

正確示例:可快速瀏覽的列表,用來代替卡片,是表現沒有許多操做的同類內容的合適方法。

以上就是Material Design 設計規範裏給的使用卡片的一些場景和正確使用方法,更多的設計規範請看:Material Design 官網

咱們要怎麼實現卡片設計呢?Google 給咱們提供了CardView,而且是像下兼容的(L 如下仍然能夠用)。CardView 的用法比較簡單,重要的屬性也就幾個。其實用CardView主要實現的圓角和陰影效果。看一下CardView的屬性:

  • app:cardBackgroundColor 設置卡片的背景色

  • app:cardCornerRadius 設置卡片的圓角

  • app:cardElevation 設置卡片的陰影

  • app:cardUseCompatPadding 是否添加padding

  • app:cardPreventCornerOverlap 在v20和v20之前的版本添加padding,防止CardView的內容和圓角相交

上面幾個屬性是CardView的幾個經常使用的屬性,固然也能夠在代碼中設置,調用CardView.setXXX就行

mCardView = (CardView) findViewById(R.id.card_view);
        //設置背景
        mCardView.setCardBackgroundColor(getColor(R.color.colorPrimary));
        //設置圓角
        mCardView.setRadius(5);
        //設置陰影
        mCardView.setCardElevation(3);
        //設置 兼容padding 
        mCardView.setUseCompatPadding(true);
        //
        mCardView.setPreventCornerOverlap(true);複製代碼

比較簡單,就上面幾個屬性,都一一介紹了,看一些示例:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:layout_margin="8dp"
    >
  <android.support.v7.widget.CardView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:cardCornerRadius="3dp"
      app:cardElevation="3dp"
      app:cardUseCompatPadding="true"
      >
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
         >
          <ImageView
              android:layout_width="match_parent"
              android:layout_height="300dp"
              android:scaleType="centerCrop"
              android:src="@drawable/meizhi"
              />
          <TextView
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:textSize="24sp"
              android:text="Material Design"
              android:textColor="@color/black"
              android:layout_marginTop="16dp"
              android:paddingRight="16dp"
              android:paddingLeft="16dp"
              />
          <TextView
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:textSize="16sp"
              android:layout_marginTop="6dp"
              android:paddingRight="16dp"
              android:paddingLeft="16dp"
              android:text=" material metaphor is the unifying theory of a rationalized space and a system of motion."
              />
          <LinearLayout
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:orientation="horizontal"
              android:padding="16sp"
              >

              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:textColor="@color/orange"
                  android:textSize="24sp"
                  android:text="SHARE"
                  />
              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:textColor="@color/orange"
                  android:textSize="24sp"
                  android:text="EXPLORE"
                  android:layout_marginLeft="20dp"
                  />
          </LinearLayout>
     </LinearLayout>
  </android.support.v7.widget.CardView>
  <android.support.v7.widget.CardView
      android:id="@+id/card_view"
      android:layout_width="match_parent"
      android:layout_height="100dp"
      app:cardBackgroundColor="@color/DarkCyan"
      app:cardUseCompatPadding="true"
      app:cardElevation="4dp"
      app:cardCornerRadius="5dp"
      >
      <TextView
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:text="Card"
          android:textColor="@color/white"
          android:textSize="20sp"
          android:gravity="center"
          />
  </android.support.v7.widget.CardView>
</LinearLayout>複製代碼

效果以下:

CardView.png

CardView 點擊效果
Material Design 的設計就是爲了更貼近現實生活中的場景,當點擊以後,是會有反饋的,能夠給CardView 添加點擊效果。

android:clickable="true"
  android:foreground="?attr/selectableItemBackground"複製代碼

這樣點擊時就會有波紋擴散效果了,增長體驗。

cardView_click.gif

FloatingActionButton (浮動操做按鈕)

FloatingActionButton(浮動操做按鈕,如下簡稱FAB)適用於特定的進階操做。它是漂浮在 UI 上的一個圓形圖標,具備一些動態的效果,好比變形、彈出、位移等等。FAB有3種尺寸,默認尺寸、mini 尺寸和 auto 尺寸 。

默認尺寸:適用於大多數狀況
mini 尺寸:僅用於建立與其餘屏幕元素視覺的連續性。
auto: 基於Window(窗口)大小變化的,當窗口大小小於470dp,會選擇一個較小尺寸的button,更大一點的窗口就選擇更大的button

能夠經過 fabSize 來控制FAB的size。由於FAB這個類繼承自ImageView,因此咱們能夠經過setImageDrawable() 方法來控制FAB icon 的顯示。FAB 默認的背景色是colorAccent,若是你想在運行時改變它的顏色,你能夠調用方法setBackgroundTintList(ColorStateList)) 來改變。

介紹一下FAB的幾個屬性:

  • app:elevation 設置陰影
  • app:rippleColor 擴散效果的顏色

  • app:fabSize 設置 FAB 的 size

  • app:layout_anchor 設置錨點

  • app:useCompatPadding 兼容padding 可用

屬性比較簡單,前面講Behavior 的時候提到過,FAB 和 AppbarLayout 的聯動和FAB和Snackbar的Behavior 確保Snackbar 從底部彈出時,不會遮擋FAB,而會相應的上移。這2個也是FAB 經常使用的場景,效果以下:

FAB.gif

佈局以下:

<?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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
  <android.support.design.widget.AppBarLayout
      android:id="@+id/appbar_layout"
      android:layout_width="match_parent"
      android:layout_height="wrap_content">

     <android.support.design.widget.CollapsingToolbarLayout
         android:layout_width="match_parent"
         android:layout_height="200dp"
         app:layout_scrollFlags="scroll|exitUntilCollapsed"
         >
          <ImageView
           android:layout_width="match_parent"
           android:layout_height="200dp"
           android:src="@drawable/meizhi"
           android:scaleType="centerCrop"
           app:layout_collapseMode="parallax"
           />
          <android.support.v7.widget.Toolbar
              android:layout_width="match_parent"
              android:layout_height="?attr/actionBarSize"
              app:layout_collapseMode="pin"
              />
     </android.support.design.widget.CollapsingToolbarLayout>
  </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="18dp"
            android:text="@string/large_text"/>

    </android.support.v4.widget.NestedScrollView>
  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fab1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@android:drawable/ic_dialog_email"
      android:layout_marginBottom="20dp"
      android:layout_marginRight="15dp"
      android:layout_gravity="bottom|right"
      app:rippleColor="@android:color/darker_gray"
      app:elevation="3dp"
      />
  <android.support.design.widget.FloatingActionButton
      android:id="@+id/fab2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_book_list"
      android:layout_marginBottom="20dp"
      android:layout_marginRight="15dp"
      app:layout_anchor="@+id/appbar_layout"
      app:layout_anchorGravity="bottom|right"
      app:elevation="5dp"
      />
</android.support.design.widget.CoordinatorLayout>複製代碼

代碼中改變FAB 顏色,icon等:

fab1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(fab1,"點擊fab1",Snackbar.LENGTH_LONG).show();
            }
        });

        fab1.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.colorPrimary)));


        fab2.setImageResource(R.drawable.ic_book_list);

        fab2.setCompatElevation(6);

        fab2.setSize(FloatingActionButton.SIZE_NORMAL);複製代碼

還能夠監聽FAB的隱藏或者顯示,在項目中可能會有這樣的需求,當FAB隱藏或者顯示以後,接下來作什麼操做,監聽代碼以下:

fab2.hide(new FloatingActionButton.OnVisibilityChangedListener() {
            @Override
            public void onHidden(FloatingActionButton fab) {
                Log.i(TAG,"fab hidden...");
            }
        });
        fab2.show(new FloatingActionButton.OnVisibilityChangedListener() {
            @Override
            public void onShown(FloatingActionButton fab) {
                Log.i(TAG,"fab show...");
            }
        });複製代碼

能夠在對應的回調方法裏作接下來的操做。

Snackbar

Snackbar 是一種針對操做的輕量級反饋機制,常以一個小的彈出框的形式,出如今手機屏幕下方或者桌面左下方。它們出如今屏幕全部層的最上方,包括浮動操做按鈕。

它們會在超時或者用戶在屏幕其餘地方觸摸以後自動消失。Snackbar 能夠在屏幕上滑動關閉。當它們出現時,不會阻礙用戶在屏幕上的輸入,而且也不支持輸入。屏幕上同時最多隻能現實一個 Snackbar。

Android 也提供了一種主要用於提示系統消息的膠囊狀的提示框 Toast。Toast 同 Snackbar 很是類似,可是 Toast 並不包含操做也不能從屏幕上滑動關閉。

用法:
Snackbar的高度應該能容納下所提示的 文本,而且提示與操做相關,因此不該該提示長文本,Snackbar的用法與Toast很是類似。

彈出一個Toast 的代碼:

Toast.makeText(FABSimpleActivity.this,"哈哈,我是Toast",Toast.LENGTH_SHORT).show();複製代碼

彈出一個snackbar的代碼:

fab2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Snackbar.make(fab2,"哈哈,我是Snackbar",Snackbar.LENGTH_SHORT).show();
            }
        });複製代碼

效果以下:

snackbar.png

從上面的代碼能夠看出,Toast與Snackbar的調用方法很是類似,第一個參數有點區別,Toast的第一個參數是一個Context,Snackbar的第一個參數是View,但其實都是異曲同工的,Snackbar也是根據傳入的View找到一個Parent View ,而後再獲取Context。或許大家跟我同樣很奇怪爲何要繞着麼大一圈來獲取這個Context,像Toast 同樣直接傳一個Context不行嗎?答案是不行的,由於須要告訴Snackbar,讓它顯示在哪一個容器內。Snackbar 和Toast的方式不同,看一下源碼一目瞭然,走讀一下源碼:
1,在make方法裏構造了Snackbar,傳入的參數是根據傳的View找到的Parent View :

public static Snackbar make(@NonNull View view, @NonNull CharSequence text, @Duration int duration) {
        Snackbar snackbar = new Snackbar(findSuitableParent(view));
        snackbar.setText(text);
        snackbar.setDuration(duration);
        return snackbar;
    }複製代碼

Snackbar 構造方法:

private Snackbar(ViewGroup parent) {
        mTargetParent = parent;
        mContext = parent.getContext();

        ThemeUtils.checkAppCompatTheme(mContext);

        LayoutInflater inflater = LayoutInflater.from(mContext);
        mView = (SnackbarLayout) inflater.inflate(
                R.layout.design_layout_snackbar, mTargetParent, false);

        mAccessibilityManager = (AccessibilityManager)
                mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
    }複製代碼

上面inflate 的時候用到了mTargetParent,告訴Snackbar要顯示在哪一個容器內。
再看一下show 的方式:

public void show() {
        SnackbarManager.getInstance().show(mDuration, mManagerCallback);
    }複製代碼

而後調用scheduleTimeoutLocked 方法:

private void scheduleTimeoutLocked(SnackbarRecord r) {
        if (r.duration == Snackbar.LENGTH_INDEFINITE) {
            // If we're set to indefinite, we don't want to set a timeout
            return;
        }

        int durationMs = LONG_DURATION_MS;
        if (r.duration > 0) {
            durationMs = r.duration;
        } else if (r.duration == Snackbar.LENGTH_SHORT) {
            durationMs = SHORT_DURATION_MS;
        }
        mHandler.removeCallbacksAndMessages(r);
        mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_TIMEOUT, r), durationMs);
    }複製代碼

最後是用Handler 發了一條消息,通知顯示:

static {
        sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                switch (message.what) {
                    case MSG_SHOW:
                        ((Snackbar) message.obj).showView();
                        return true;
                    case MSG_DISMISS:
                        ((Snackbar) message.obj).hideView(message.arg1);
                        return true;
                }
                return false;
            }
        });
    }複製代碼

看到這兒大概就明白了,最終調用的是showView()這個方法顯示:

final void showView() {
...
// 上面省略的部分主要是判斷是否是CoordinatorLayout的子View,若是添加Behavior 

if (ViewCompat.isLaidOut(mView)) {
            if (shouldAnimate()) {
                // If animations are enabled, animate it in
                animateViewIn();
            } else {
                // Else if anims are disabled just call back now
                onViewShown();
            }
        } else {
            // Otherwise, add one of our layout change listeners and show it in when laid out
            mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View view, int left, int top, int right, int bottom) {
                    mView.setOnLayoutChangeListener(null);

                    if (shouldAnimate()) {
                        // If animations are enabled, animate it in
                        animateViewIn();
                    } else {
                        // Else if anims are disabled just call back now
                        onViewShown();
                    }
                }
            });
        }
}複製代碼

以上分析了Snackbar 的建立顯示過程。其實使用是很簡單的,跟之前使用Toast提示差很少。

最後還有一點就是,Toast只能給個提示,而Snackbar咱們還能夠給他設置一個Action,當顯示Snackbar的時候,咱們點擊Action按鈕,執行相應的操做

代碼:

private void showSnackbar(){
        Snackbar snackbar = Snackbar.make(fab2,"哈哈,我是Snackbar",Snackbar.LENGTH_SHORT);
        snackbar.setAction("UNDO", new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(FABSimpleActivity.this,"執行Undo操做",Toast.LENGTH_LONG).show();
            }
        });

        snackbar.setActionTextColor(getResources().getColor(R.color.DarkCyan));
        snackbar.setText("已經刪除1張照片");
        snackbar.show();
    }複製代碼

效果以下:

snackbar_action.gif

如上圖所示,添加了一個UNDO 按鈕,點擊按鈕之行相應操做。

最後

這個三個控件的用法比較簡單,本文從它們的使用場景和原理講了它們的基本用法,瞭解這些以後,能夠加深印象。本文是Material Design 相關的最後一篇文章,可能還有一些零碎的東西沒有講到,還有一些像RecyclerView 這些的用法網上的博客已經不少了,有的也寫得很好很詳細,不打算再寫。另外,Material Design 系列的Demo在這兒:MaterialDesignSamples

參考
Material Design 設計規範
Material Design 中文版

相關文章
相關標籤/搜索