一文徹底搞清楚 Material Design
首先宣告以下介紹的關於Material Design
的介紹,都是基於在 Android
環境下,其實 Material Design
是一種為了讓 UI 頁面更加美觀的設計規範,也可以按照這種規範應用到 iOS
、Web
上。
Material Design
是 Google 在 2014 年 I/O
大會上釋出的一種新的設計規範。這種設計風格給 Android UI 設計帶來了很多的變化。讓頁面變得美感十足。
Material Design
是一種綜合了傳統優秀的設計和科技創新的設計語言。
Material Design 的設計靈感來自現實世界中真正的物質材料。Material Design 設計語言強調根據使用者行為凸顯核心功能,進而為使用者提供操作指引,通過鮮明、形象的顏色差。新增合適的動作來引導使用者。
Material Design
強調真實性,有立體感。Material Design 的三維體現在光、繪製面和投射陰影。 所有的材料物件都包含 x,y,z 三個維度。z 軸代表了海拔高度,而不是材料的厚度,這一點很多資料都是錯誤的。材料的厚度永遠是 1 dp 不能改變。x ,y 就是對應了材料的長寬,可以改變。
這裡的材料在Android
世界中就是一個個的控制元件,我們可以把控制元件想象成現實世界中的物體,規定每個物體的厚度都是固定不變的,永遠是 1dp
,x,y
就對應了控制元件的長和寬。
為了體現出真實物體的感覺,引入了光,陰影等一些概念,這些概念我們下面會一一說明。
為了配合這種設計規範,Android 又推出了許多相關的控制元件。這些控制元件你既可以單獨引用,也可以直接通過android.design
包來引入。為了配合 material desig, android 提供了新的主題、新的配合主題的元件、和自定義陰影和新動畫 api
來看看 Android 為了配合 Material Design
都增加了哪些新的控制元件:
一些基本概念
3D
在真實的物質世界裡面,是一個三維的環境。所有的物體都有 x,y,z三個維度。在 Material Design 中,每個物體(也就是你的控制元件)都有 1 dp 的厚度。
然後這些控制元件還有海拔的概念,還有影子的概念,這些就體現出了 3 D的感覺。
Z 的概念
因為強調現實世界的真實性,引入Z
代表了控制元件的海拔高度。比如說:在一個桌面上,你放了一本書 A,然後在 A 上又放了一本書 B 。這個時候肯定會有層次感,B 相對於桌面的海拔高度和 A 相對於桌面的海拔高度肯定是不一樣的。在 Android 中就用 Z 來代表控制元件的海拔高度。
為了滿足 Material Design
的層次要求,android 5.0 後增加了 Z 軸,用來表示控制元件的海拔,海拔的效果具體體現在陰影上。
Z = elevation + translationZ
View 中的 Z
的值有兩部分組成:
注意對 translationZ 的設定,如果單純的設定控制元件高度的話,應該是設定 elevation
。而不是 translationZ
-
elevation
:海拔高度,用來指定控制元件靜止海拔高度elevation
屬性 也可以在程式碼中通過setElevation
來設定。在 Android 中 elevation 這個屬性代表了海拔高度,這個值是永遠有效的,只是如果沒有陰影的話,可能體現不出來,只能通過下面的海拔演示來體現出來。
-
TranslationZ
: 動態海拔高度偏移高度,是一個偏移的距離,是用來作動畫效果,否則不要使用。Translation Z 是動態的,當建立一個專案,增加一個按鈕,當按下按鈕會陰影變大了。實際上 Elevation 並沒有變化,而是 Translation Z 屬性在變化。這是 Android 使用預設的狀態列表動畫,更改 Z 屬性。
按鈕的動作效果,預設 FAB 有 6dp 的Elevation,當按下按鈕時 translation Z 值開始增加。ViewPropertyAnimator 通過將 translation Z 的值從 0 dp改為 6 dp 來讓檢視動起來。如果釋放按鈕,ViewPropertyAnimator 播放動畫,將 translationZ 從 6 dp變到 0 dp。我們可以給我們的檢視建立自定義狀態列表動畫,新增到檢視上。
Z 屬性會擴大 View 的顯示區域(主要是控制元件本身大小+陰影),如果它的大小大於或者等於父檢視的大小,那麼它的陰影效果就無法顯示了,view 並不會因為 z 的屬性而縮小自身去顯示陰影。
Z屬性不僅影響著view的陰影效果,還影響著view的繪製順序,在同一個父view內部,Z屬性越小,繪製的時機就越早。也就是優先被繪製,而z屬性越大,則繪製時間越晚,後繪製的將會遮蓋住先繪製的,只有Z屬性相同,才按照新增的順序繪製。
海拔
其實上面介紹Z
的時候就介紹海拔了,海拔就是為了表現層次感所引入的,現實世界中都有海拔的概念,Z 的值就是代表了海拔。
海拔高度指的是從一個表面到另一個表面之間的距離,元素的海拔高度指明瞭元素表面之間的距離以及陰影的深度。
海拔高度是兩個表面在 Z 軸上的距離,單位也是使用的 dp,一個子元素的海拔是相對於父元素而言的。
海拔高度分為:靜止狀態海拔高度和動態海拔高度偏移。
靜態狀態海拔高度:所有的元素都有一個靜止的海拔高度(elevation)。
動態海拔高度偏移:指的是從靜止狀態向目標海拔移動的距離(translationZ)
元件的海拔高度:
- 同一元件在不同的應用中,海拔高度是相同的,比如:不同應用中的浮動操作按鈕的海拔是相同的
- 同一元件在不同的平臺和裝置中,可能會有不同的海拔高度,這主要和環境深度有關。比如:電視具有比桌面更大的深度,因為螢幕更大,使用者觀看的距離更遠。同樣電視和桌面的深度比移動裝置更深。
某些型別的元件具有響應式的海拔高度,會根據使用者的輸入(例如 正常狀態、獲取焦點、按下)和系統事件來改變自身的海拔。這些海拔高度的改變通常是通過動態海拔高度偏移來實現的。
動態海拔高度偏移是元件從靜止海拔高度向目標海拔高度所移動的距離。所有元件在被按下時,預設所增加的海拔高度是一樣的。一旦輸入事件完成或取消,元件會回到原來靜止的海拔高度。
這張圖中,控制元件的海拔高度就不同,表現出層次感。
比如這張圖,手機螢幕可以當做是水平面,海拔高度為0,上面有很多控制元件,它們的海拔高度是不一樣的,就表現出層次感了。
海拔的演示
比如 CardView 和 TextView
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="200dp">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name"/>
</RelativeLayout>
這樣的話 TextView 是不會顯示出來的,因為 TextView 的預設海拔是0 ,就被 Cardview 給擋住了,因為 CardView 的預設海拔是 2dp,如果你將 TextView 的海拔設定為 3dp 這個時候 TextView 就可以顯示了。
複製程式碼
一般控制元件的標準海拔
- 應用欄:4dp
- 按鈕:靜止狀態 2dp 按下狀態:8dp
- 浮動操作按鈕(FAB)靜止:6dp 按下:12dp
- 卡片 靜止:2dp 浮動狀態:8dp
- 選單和子選單:選單:8dp 子選單:9dp(每個子選單+1)
- 對話方塊 24dp
- 抽屜式導航 16dp
- 重新整理指示器 3dp
- 快速入口/搜尋欄 靜止2dp 滾動3dp
- snackbar 6dp
- 開關 1dp
物體的層級結構
所有的物體都是根據父-子關係描述的,子元素會繼承父元素的變化屬性,比如位置、旋轉、海拔高度。同級的物體在層次結構中屬於同一層。
比如說:我們桌子上有一層紙,如果我們再貼一張紙,我們的眼睛就會覺得有一個深度。
同樣的效果,左邊就有深度的感覺,有層次感。
深度(Depth)
深度(depth)的意思就是材質環境中所有的元素都是沿著 Z 軸水平、垂直和以不同的深度移動,在 Z 軸的正方向並且在可是範圍內的點的高度。其實就海拔。
輪廓
預設情況下,所有的view都是矩形的,雖然可以給view設定背景圓形的圖片,即可以在介面顯示出圓形的內容,但是view的大小實際上依然是矩形,並且設定的圖片實際上也是矩形的,只是圓形以外的區域是透明色。
如果根據view大小來生成對應的陰影,就會出現很奇怪的效果,(一個看起來圓形的view展示出的確實一個矩形的陰影)為了解決這個問題,view增加了一個新的描述來指明內容顯示的形狀,這就是輪廓。
輪廓(Outlines) 代表圖形物件的外形狀,並確定了對於觸控反饋的波紋區域。
每個 view 都有預設的輪廓(其實有的 View 也沒有預設的輪廓,比如 TextView)。如果我們自定義一個 View,其輪廓應該由我們自己來實現其輪廓。
輪廓的實現
①通過shape設定的背景,view會自動根據shape的形狀進行輪廓判定, ②通過color設定的背景,view預設其輪廓和view的大小一樣。 ③但是通過圖片進行背景設定,view則無法獲知輪廓的形狀,這個時候就需要手動進行指定了
手動指定輪廓
當預設輪廓不好使,或者是我們自己定義的View 的話,就需要我們自己通過程式碼來指定輪廓了。
在程式碼中,可以通過setOutlineProvider來指定一個view的輪廓。
與輪廓有關的類 Outline
Outline是在 android.graphic 下的類,文件說明:
定義一個簡單的形狀,用於作為圖形的邊界區域
可以作為一個 View 計算,可以由 Drawable 計算,用來驅動投射出的陰影形狀,或者裁剪 View 的內容
總之,這個類就是用來給View指定輪廓的。View 的輪廓還可以通過 outlineProvider
屬性來設定,預設是 background
還有其他值bounds none paddingBounds
none:即使你設定了 evaluation 也會顯示陰影
background:按背景來顯示輪廓,如果 background 是顏色值,則輪廓就是 view 的大小,如果是 shape 則按shape指定的形狀來作為輪廓,顯示陰影 如果 background 是圖片或者透明shape的話只能用程式碼 `setOutlineProvider()` 來指定輪廓了
bounds:View 的矩形大小作為輪廓
paddingBounds:View 的矩形大小減去 padding 的值後的大小做輪廓 paddedBounds 和bounds類似,不過陰影會稍微向右偏移一點
複製程式碼
如果我們想建立一個自定義檢視,並動態地去改變它的輪廓,這個時候需要使用 ViewOutlineProvider
通過ViewOutlineProvider
這個類我們可以自己給 View 新增輪廓
public class MyViewOutlineProvider extends ViewOutlineProvider {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, width, height, radius);
}
}
// 這樣這個 View 就有輪廓了,然後通過 setElevation 來修改海拔就可以出現陰影了
//這個方法是提供輪廓,具體的陰影通過 Z 來設定,在輪廓大小固定的情況下,修改 Z 的大小,會佔用輪廓的空間,看上去輪廓在變小。
view.setOutProvider(new MyViewOutlienProvider);
// 如果不想讓檢視有投射陰影,可以設定輪廓提供者為 null
複製程式碼
裁剪
View 的裁剪是指將 View 按照輪廓裁剪,能改變 View 的形狀,如圓形頭像:
-
先設定輪廓
-
在設定根據輪廓裁剪 View,目前只支援對矩形、圓形、圓角矩形的裁剪
tvClip.setClipToOutline(true)// 設定對 View 進行裁剪
通過 outlin.canClip() 方法來檢查是否支援擦肩。
陰影
上面介紹了 3D、海拔、輪廓這些基本的概念,其實這些概念最終有體現效果就是靠陰影。
陰影是一個重要的視覺提示,表示了物體的海拔和運動方向。也是指示兩個面之間距離的唯一視覺元素。物體的海拔高度決定了陰影的外觀。
陰影還可以用來表示物體的運動方向、表面之間的距離是增加還是減少。
陰影提供了關於海拔、運動方向和繪製邊緣的提示。不同的海拔高度,陰影的表現效果是不同的。
一般來說海拔越高,陰影越大,越低陰影越小,但是海拔太大會出現陰影消失的現象(一般是超過20dp)。當物質材料表面比例改變的時候,其陰影不應該發生改變,海拔髮生了變化的時候,其陰影要發生改變。
物質材料內部可以展示任何形狀和顏色,但其內容不會增加材料的厚度。
陰影的產生是不同海拔高度的材料相互疊加產生的,在 Material Design 中,虛擬的光線照射使我的物質材料出現陰影,這裡的光有兩種光,一種是關鍵燈,一種是環境燈。關鍵燈會建立更加銳利的方向性陰影,稱為關鍵陰影。環境光從各個角度出現,建立擴散的柔和陰影,稱為環境陰影。
材質環境中的陰影由關鍵燈光和環境燈光投射共同產生。 在Android和iOS開發中,當光源在沿z軸的各個位置處被“材質”表面阻擋時,會出現陰影。 在Web上,僅通過操縱y軸即可描繪陰影。 以下示例顯示了海拔為6dp的卡片。
陰影的條件
陰影由輪廓和海拔共同決定。
海拔決定了陰影的大小,輪廓決定了陰影的形狀。
陰影一定需要有輪廓然後海拔增高後才能被投射出來,兩者缺一不可。陰影的底層是 native
實現的而不是普通的 2D 漸變效果模擬陰影。
在 Android L 中設定陰影只需兩點
- 設定海拔高度(通過 elevation)
- 設定輪廓
Button 單純的施加 elevation
是沒有陰影效果的,因為 Button 的陰影效果由 stateListAnimatior
來決定了,如果想要自己給 Button
新增的 elevation
有效果的話,必須將 stateListAnimator = "@null"
才可以。但是如果stateListAnimator
設定為 null 後,點選的海拔高度動畫就沒有了,為此你可以在 Button 外套一層 LinearLayout
給 LinearLayout 設定 elevation
,記住LinearLayout
一定要有背景 。但是設定最好不需要這樣,用 Button 自身的陰影效果就可以了,它的陰影會根據 Button 在頁面中的位置的不同陰影還不同。 詳見 Button