記得剛開始學Android時,看著自己完全用系統控件寫出的不忍直視的界面,對于如何做出不一樣的按鈕,讓它們在不同狀態(tài)下有不一樣的效果很是好奇。后來才知道了些shape,selector之類,但很長時間以來都沒對樣式開發(fā)做過全面的整理(主要因為懶...),所以有了這篇文章。
好了廢話不多說,開始我們的樣式開發(fā)之旅。因為整理了基本所有的樣式,文章可能有點長,可以收藏下來看,當然看的時候也要自己試試效果,不然很容易過幾天又忘記了,下次用的時候還要翻一遍。
本文將按下面幾個模塊講:
1. shape:基礎的形狀定義
2. selector:不同狀態(tài)下的形狀變化
3. layer-list:多個形狀層疊
4. drawable:各式drawable和drawable動畫
5. View Animation:簡單的視圖動畫
6. Property Animation:更強大的屬性動畫
7. style和theme
1. shape
shape用來定義基本的形狀,一般項目的drawable目錄下,布局中各view通過設置android:background屬性來引用。
1.1 rectangle(矩形,默認的形狀)
新建一個drawable resource file,取名為bg_btn,來為按鈕定義形狀,根節(jié)點默認是selector,改成shape,shape下能設置多個特性,我們一個一個來看
solid:設置形狀填充的顏色,只有android:color一個屬性
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/holo_blue_dark"/>
</shape>
效果和直接設置view的background為顏色一樣
padding:設置內容與形狀邊界的內間距,可分別設置左右上下的距離
- android:left 左內間距
- android:right 右內間距
- android:top 上內間距
- android:bottom 下內間距
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@android:color/holo_blue_dark"/>
<padding android:top="16dp"
android:bottom="16dp"/>
</shape>
當然也和直接設置view的padding一樣,不過當你的shape多次使用時,在shape中設置padding就免去為每個控件單獨設置padding
gradient:設置形狀的漸變顏色,可以是
線性漸變(linear,默認的漸變類型)
輻射漸變(radial ,以圓形從圓形向周圍漸變,android:gradientRadius也必須設置)
掃描性漸變(sweep ,以一條水平線逆時針掃描漸變)
- android:type 漸變的類型
- android:startColor 漸變開始的顏色
- android:endColor 漸變結束的顏色
- android:centerColor 漸變中間的顏色
- android:angle 漸變的角度,線性漸變時才有效,必須是45的倍數,0表示從左到右,90表示從下到上
- android:centerX 漸變中心的相對X坐標,放射漸變時才有效,在0.0到1.0之間,默認為0.5,表示在正中間
- android:centerY 漸變中心的相對X坐標,放射漸變時才有效,在0.0到1.0之間,默認為0.5,表示在正中間
- android:gradientRadius 漸變的半徑,漸變類型為radial時使用
linear漸變效果
<gradient android:type="linear"
android:startColor="#18d1ff"
android:centerColor="#4953fd"
android:endColor="#5718bc"
android:angle="0"/>
</shape>
這里的漸變方向由android:angle來設置
radial漸變效果
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:type="radial"
android:startColor="#18d1ff"
android:centerColor="#4953fd"
android:endColor="#5718bc"
android:gradientRadius="160dp"/>
</shape>
漸變中心由(centerX, centerY)確定,比如我們都把它設為0
sweep漸變效果
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:type="sweep"
android:startColor="#18d1ff"
android:centerColor="#4953fd"
android:endColor="#5718bc"/>
</shape>
corners:設置圓角,只適用于rectangle類型
- android:radius 圓角半徑,會被下面每個具體的圓角屬性覆蓋
- android:topLeftRadius 左上角的半徑
- android:topRightRadius 右上角的半徑
- android:bottomLeftRadius 左下角的半徑
- android:bottomRightRadius 右下角的半徑
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="#0564fe"
android:centerColor="#3b45ef"
android:endColor="#6f28e0"/>
<corners android:radius="24dp"/>
</shape>
分別設置topLeftRadius,topRightRadius,bottomLeftRadius
stroke:設置描邊,實線或虛線
- android:color 描邊的顏色
- android:width 描邊的寬度
- android:dashWidth 設置虛線時的橫線長度
- android:dashGap 設置虛線時的橫線之間的距離
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:color="@android:color/darker_gray"
android:width="2dp"/>
</shape>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke android:color="@android:color/darker_gray"
android:width="2dp"
android:dashWidth="4dp"
android:dashGap="6dp"/>
</shape>
1.2 oval(橢圓形,常用來畫圓形)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@android:color/holo_blue_dark"/>
<gradient android:type="radial"
android:startColor="#ffffff"
android:endColor="#a1a1a1"
android:gradientRadius="64dp"/>
<size android:height="128dp"
android:width="128dp"/>
</shape>
這里有個size節(jié)點,它是用來設置形狀大小的,但一般不用它,比如我們現在把這個圓形設置給一個長寬不相等的控件,那效果還是橢圓,因為shape會被拉伸或壓縮。不過我用到radial漸變,想讓它從圓心剛好漸變到圓周,所以設置了size,并設置gradientRadius為size長寬的一半,這樣無論設置給多大的控件都能正確顯示
1.3 line(線形,畫實線和虛線)
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="line">
<stroke android:color="@android:color/darker_gray"
android:width="2dp"
android:dashWidth="4dp"
android:dashGap="6dp"/>
</shape>
畫線一般用來分隔內容,上面我們畫了一條虛線,把它設置給View,運行出來你發(fā)現怎么變成實線了,我要的虛線呢,這不是坑嗎??? (╯‵□′)╯︵┴─┴
原來要想顯示出虛線要給View設置android:layerType,為"software"
<View
android:layout_width="match_parent"
android:layout_height="16dp"
android:layerType="software"
android:background="@drawable/bg_div"/>
畫線時,有幾點特性必須要知道的:
- 只能畫水平線,畫不了豎線;
- 線的高度是通過stroke的android:width屬性設置的;
- size的android:height屬性定義的是整個形狀區(qū)域的高度;
- size的height必須大于stroke的width,否則,線無法顯示;
- 線在整個形狀區(qū)域中是居中顯示的;
- 線左右兩邊會留有空白間距,線越粗,空白越大;
- 引用虛線的view需要添加屬性android:layerType,值設為"software",否則顯示不了虛線。
1.4 ring(環(huán)形,可以畫環(huán)形進度條)
shape根元素有些屬性只適用于ring類型,先來看看這些屬性吧:
- android:innerRadius 內環(huán)的半徑
- android:innerRadiusRatio 浮點型,以環(huán)的寬度比率來表示內環(huán)的半徑,默認為3,表示內環(huán)半徑為環(huán)的寬度除以3,該值會被android:innerRadius覆蓋
- android:thickness 環(huán)的厚度
- android:thicknessRatio 浮點型,以環(huán)的寬度比率來表示環(huán)的厚度,默認為9,表示環(huán)的厚度為環(huán)的寬度除以9,該值會被android:thickness覆蓋
- android:useLevel 一般為false,否則可能環(huán)形無法顯示,只有作為LevelListDrawable使用時才設為true
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="ring"
android:innerRadiusRatio="3"
android:thicknessRatio="12"
android:useLevel="false">
<gradient
android:startColor="@android:color/white"
android:endColor="@android:color/holo_blue_dark"
android:type="sweep" />
<stroke
android:width="2dp"
android:color="@android:color/darker_gray" />
</shape>
用innerRadiusRatio和thicknessRatio控制環(huán)內環(huán)和厚度占比是,兩個倒數相加不能超過1/2,不然就不是環(huán)了,比如這里1/3 + 1/12 < 1/2
但這進度條不會轉叫什么進度條,下面會講到如何讓它轉起來
2. selector
上面我們講了如何定義各種形狀,并設置給控件,但開發(fā)中如果想讓在不同狀態(tài)下展示不同形狀,比如按鈕正常狀態(tài)下一種形狀,點擊是另一種形狀,這樣對用戶交互比較好,能讓用戶得到他操作后的反饋,這就要用到selector。
那首先有必要知道都有哪些狀態(tài):
- android:state_enabled: 設置觸摸或點擊事件是否可用狀態(tài),一般只在false時設置該屬性,表示不可用狀態(tài)
- android:state_pressed: 設置是否按壓狀態(tài),一般在true時設置該屬性,表示已按壓狀態(tài)
- android:state_selected: 設置是否選中狀態(tài),true表示已選中,false表示未選中
- android:state_checked: 設置是否勾選狀態(tài),主要用于CheckBox和RadioButton,true表示已被勾選,false表示未被勾選
- android:state_checkable: 設置勾選是否可用狀態(tài),類似state_enabled,只是--state_enabled會影響觸摸或點擊事件,而state_checkable影響勾選事件
- android:state_focused: 設置是否獲得焦點狀態(tài),true表示獲得焦點,默認為false,表示未獲得焦點
- android:state_window_focused: 設置當前窗口是否獲得焦點狀態(tài),true表示獲得焦點,false表示未獲得焦點,例如拉下通知欄或彈出對話框時,當前界面就會失去焦點
- android:state_activated: 設置是否被激活狀態(tài),true表示被激活,false表示未激活,API Level 11及以上才支持,可通過代碼調用控件的setActivated(boolean)方法設置是否激活該控件
- android:state_hovered: 設置是否鼠標在上面滑動的狀態(tài),true表示鼠標在上面滑動,默認為false,API Level 14及以上才支持
一看到這么多狀態(tài)是不是感覺頭疼,不過平時用到比較多的也就 state_enabled,state_pressed,state_selected,輸入框可能會用到state_focused
接下來,看看代碼,以下是bg_btn_selector.xml的代碼,用于按鈕的背景,如果只是背景顏色的變化可以在res下新建一個color文件夾在里面創(chuàng)建selector作為color資源,如果是形狀的變化則只能放到drawable下作為drawable資源,也是通過android:background設置給view:
color\bg_btn_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@android:color/darker_gray"/>
<item android:state_pressed="true" android:color="@android:color/holo_blue_dark"/>
<item android:state_selected="true" android:color="@android:color/holo_blue_dark"/>
<item android:color="@android:color/holo_blue_light"/>
</selector>
android:color只能引用@color
drawable\bg_btn_selector.xml
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:drawable="@drawable/bg_btn_disable"/>
<item android:state_pressed="true" android:drawable="@drawable/bg_btn_pressed"/>
<item android:state_selected="true" android:drawable="@drawable/bg_btn_selected"/>
<item android:drawable="@drawable/bg_btn_normal"/>
</selector>
android:drawable指定的都是定義的shape,它除了引用@drawable資源,也可以引用@color顏色值
注意:狀態(tài)是從上往下匹配的,如果匹配到一個item那它就將采用這個item,而不是采用最佳匹配的規(guī)則;所以設置默認的狀態(tài),一定要寫在最后,如果寫在前面,則后面所有的item都不會起作用了。
除此之外,selector標簽下有兩個比較常用的屬性,會在狀態(tài)改變時出現淡入淡出效果,但必須在API Level 11及以上才支持:
- android:enterFadeDuration 狀態(tài)改變時,新狀態(tài)展示時的淡入時間,以毫秒為單位
- android:exitFadeDuration 狀態(tài)改變時,舊狀態(tài)消失時的淡出時間,以毫秒為單位
3. layer-list
shape只能畫出矩形,橢圓,線,環(huán)這些基本的形狀,有時我們需要一些稍微復雜些的形狀,一種解決方案是使用圖片,但圖片要準備不同尺寸以適應不同分辨率手機,太多圖片資源會造成安裝包過大,其實可以用layer-list來自己實現一些復雜形狀
先來看看要實現效果
一個帶陰影的button,未點擊時陰影窄且深,按壓時陰影變寬且顏色變淺。它首先是selector實現
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@drawable/bg_btn_pressed"/>
<item android:state_selected="true" android:drawable="@drawable/bg_btn_selected"/>
<item android:drawable="@drawable/bg_btn_normal"/>
</selector>
來看bg_btn_pressed,bg_btn_selected它們是一樣的
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--灰色背景-->
<item>
<shape>
<solid android:color="#c4c4c4"/>
<corners android:radius="12dp"/>
</shape>
</item>
<!--藍色前景-->
<item android:bottom="6dp"
android:right="6dp">
<shape>
<solid android:color="@android:color/holo_blue_dark"/>
<corners android:radius="12dp"/>
</shape>
</item>
</layer-list>
layer-list作為根節(jié)點,也可以作為selector中item的子節(jié)點。layer-list可以添加多個item子節(jié)點,每個item子節(jié)點對應一個drawable資源,按照item從上到下的順序疊加在一起,再通過設置每個item的偏移量就可以看到陰影效果了。
layer-list的item可以通過下面四個屬性設置偏移量:
- android:top 頂部的偏移量
- android:bottom 底部的偏移量
- android:left 左邊的偏移量
- android:right 右邊的偏移量
這四個偏移量和控件的margin設置差不多,都是外間距的效果。如何不設置偏移量,前面的圖層就完全擋住了后面的圖層,從而也看不到后面的圖層效果了。
然后是bg_btn_normal.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--透明背景-->
<item>
<shape>
<solid android:color="@android:color/transparent"/>
<corners android:radius="12dp"/>
</shape>
</item>
<!--灰色背景-->
<item android:bottom="3dp"
android:right="3dp">
<shape>
<solid android:color="#bebebe"/>
<corners android:radius="12dp"/>
</shape>
</item>
<!--藍色前景-->
<item android:bottom="6dp"
android:right="6dp">
<shape>
<solid android:color="@android:color/holo_blue_light"/>
<corners android:radius="12dp"/>
</shape>
</item>
</layer-list>
第一個item是一個透明的shape,再在它上面加陰影效果,應為直接加陰影的話,normal狀態(tài)時button的大小要比按壓時大,看起來不太好。這里保證藍色前景也就是按鈕大小不變的情況下,按壓時將透明shape那部分變成陰影,并且陰影變淡,這就達到效果了
4. drawable
除了上面講的shape,selector,layer-list,Android還有很多其他drawable資源
4.1 普通圖片
圖片格式可以是png(最常用,推薦使用)、jpg、gif,用圖片資源需要根據不同屏幕密度提供多張不同尺寸的圖片:
密度分類 | 密度值范圍 | 代表分辨率 | 圖標尺寸 |
---|---|---|---|
mdpi | 120~160dpi | 320x480px | 48x48px |
hdpi | 160~240dpi | 480x800px | 72x72px |
xhdpi | 240~320dpi | 720x1280px | 96x96px |
xxhdpi | 320~480dpi | 1080x1920px | 144x144px |
xxxhdpi | 480~640dpi | 1440x2560px | 192x192px |
一張圖片一般需要提供5張不同比例的圖片,切圖的話可以用下面這個網站在線切
http://icon.wuruihong.com/
Android Studio中也可以用Image Assetl來導入符合Materical Design設計的應用常用圖標
4.2 Vector Drawable
普通圖片需要提供多張不同尺寸的圖片,這會加大安裝包的大小。而從Android 5.0開始支持的Vector可以自動進行適配,不需要通過分辨率來設置不同的圖片,可以大幅減少圖像的體積,同樣一張圖,用Vector來實現,可能只有PNG的幾十分之一
利用Android Studio的Vector Asset,可以非常方便的創(chuàng)建Vector圖像,也可以直接通過本地的SVG圖像來生成Vector圖像
4.3 level-list
level-list管理一組drawable,每個drawable設置一組level范圍,最終會根據level值選取對應的drawable繪制出來。level-list通過添加item子標簽來添加相應的drawable,其下的item只有三個屬性:
- android:drawable 指定drawable資源,如果不設置該屬性,也可以定義drawable類型的子標簽
- android:minLevel 該item的最小level值
- android:maxLevel 該item的最大level值
用它來實現比如電量,信號強弱顯示
<?xml version="1.0" encoding="utf-8"?>
<level-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:maxLevel="100" android:minLevel="100"
android:drawable="@drawable/ic_battery_full_black_24dp"/>
<item android:maxLevel="99" android:minLevel="90"
android:drawable="@drawable/ic_battery_90_black_24dp"/>
<item android:maxLevel="89" android:minLevel="80"
android:drawable="@drawable/ic_battery_80_black_24dp"/>
<item android:maxLevel="79" android:minLevel="60"
android:drawable="@drawable/ic_battery_60_black_24dp"/>
<item android:maxLevel="59" android:minLevel="50"
android:drawable="@drawable/ic_battery_50_black_24dp"/>
<item android:maxLevel="49" android:minLevel="30"
android:drawable="@drawable/ic_battery_30_black_24dp"/>
<item android:maxLevel="29" android:minLevel="20"
android:drawable="@drawable/ic_battery_20_black_24dp"/>
<item android:maxLevel="19" android:minLevel="0"
android:drawable="@drawable/ic_battery_alert_black_24dp"/>
</level-list>
設置給ImageView
<ImageView
android:id="@+id/iv_battery"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/battery_level"/>
代碼中通過getDrawable().setLevel(level)設置level,這里每點一下減少10%電量
level-=10;
tvbattery.setText(level+"");
ivbattery.getDrawable().setLevel(level);
item的匹配規(guī)則是從上到下的,當設置的level值與前面的item的level范圍匹配,則采用。當從大到小排序下來,可以去掉每個item的android:maxLevel屬性,比如上面,從小到大則可以去掉android:minLevel
4.4 animation-list
animation-list可以將一系列drawable構建成幀動畫,就是將一個個drawable,一幀一幀的播放。通過添加item子標簽設置每一幀使用的drawable資源,以及每一幀持續(xù)的時間。
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/ic_wifi_signal_0" android:duration="1000"/>
<item android:drawable="@drawable/ic_wifi_signal_1" android:duration="1000"/>
<item android:drawable="@drawable/ic_wifi_signal_2" android:duration="1000"/>
<item android:drawable="@drawable/ic_wifi_signal_3" android:duration="1000"/>
<item android:drawable="@drawable/ic_wifi_signal_4" android:duration="1000"/>
</animation-list>
- android:oneshot屬性設置是否循環(huán)播放,設為true時,只播放一輪就結束,設為false時,則會輪詢播放。
- android:duration屬性設置該幀持續(xù)的時間,以毫秒數為單位。
設置給ImageView,在代碼中
((AnimationDrawable)iv.getDrawable()).start();
animation-list對應的Drawable類為AnimationDrawable,要讓動畫運行起來,需要主動調用AnimationDrawable的start()方法。另外,如果在Activity的onCreate()方法里直接調用start()方法會沒有效果,因為view還沒有初始化完成是播放不了動畫的。
4.5
animated-rotate會讓drawable不停地做旋轉動畫(不用在代碼中啟動)
animated-rotate可設置的屬性只有四個:
- android:drawable 指定drawable資源,如果不設置該屬性,也可以定義drawable類型的子標簽
- android:pivotX 旋轉中心的X坐標
- android:pivotY 旋轉中心的Y坐標
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/bg_progressv"
android:pivotX="50%"
android:pivotY="50%">
</animated-rotate>
注意:50%不能寫成0.5, 它們是不同的意思,50%是相對于控件,0.5是相對于整個屏幕
5. View Animation
未完