前言
更多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。由於用法比較簡單,所以就不每一個都拎出來單講。以下分別是這三個控制元件的用法。
CardView(卡片)
卡片是一張帶有材料屬性的紙片,用作展示更多詳細資訊的入口點。卡片包含了一組特定的資料集,資料集含有各種相關資訊,如主題照片、文字,連結等等。卡片有固定的寬度和可變的高度。最大高度限制於可適應平臺上單一檢視的內容,但如果需要它可以臨時擴充套件(例如,顯示評論欄)。卡片不會翻轉以展示其背後的資訊。
卡片集是共面的,或者統一平面的多張卡片佈局。如下:
一張卡片包含了一組特定的資料集,考慮在以下這些情況使用卡片:
作為一個集合,比較多種資料型別,比如:圖片、視訊和文字。
不需要直接比較(如:使用者不會直接比較圖片和文字)
支援內容高度可變,比如評論。
包含響應按鈕,比如+1 按鈕或者評論
要使用網格列表,但需要顯示更多文字來補充圖片
以上就是使用卡片的一些場景,看一個使用不當的例子(圖片來自官網):
錯誤示例: 這種卡片的使用分散了使用者的注意力,不能快速瀏覽,也不能忽略掉,所以將這些內容放在不同的卡片上是難以理解的。其實國內有些知名APP也沒有按照規範來做,給我們做了錯誤的示範,如知乎日報首頁:
正確的用法如下:
正確示例:可快速瀏覽的列表,用來代替卡片,是表現沒有許多操作的同類內容的合適方法。
以上就是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 點選效果
Material Design 的設計就是為了更貼近現實生活中的場景,當點選之後,是會有反饋的,可以給CardView 新增點選效果。
android:clickable="true"
android:foreground="?attr/selectableItemBackground"複製程式碼
這樣點選時就會有波紋擴散效果了,增加體驗。
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 常用的場景,效果如下:
佈局如下:
<?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();
}
});複製程式碼
效果如下:
從上面的程式碼可以看出,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();
}複製程式碼
效果如下:
如上圖所示,新增了一個UNDO 按鈕,點選按鈕之行相應操作。
最後
這個三個控制元件的用法比較簡單,本文從它們的使用場景和原理講了它們的基本用法,瞭解這些之後,可以加深印象。本文是Material Design 相關的最後一篇文章,可能還有一些零碎的東西沒有講到,還有一些像RecyclerView 這些的用法網上的部落格已經很多了,有的也寫得很好很詳細,不打算再寫。另外,Material Design 系列的Demo在這兒:MaterialDesignSamples