Android style & Theme 再探析(四)
業務實踐心得總結
基於前幾篇的探究,我們已經有了對一個app的theme和style的理論探究和demo描述了不少,那麼接下來針對我在自己在app生產環境中的使用的一個使用心得總結
Android Theme的生產環境使用總結
本篇主要針對小夥伴如果要在自己的app中實踐,整理以前混亂的theme和style的一個實踐
針對Theme配置的一些建議
如果大家按本文第一章的方式設定Theme時,一定要注意幾點:
文字顏色一定要設定小心,由於Theme具有繼承性,所以文字顏色的設定會被Android本身的style的繼承結構沿用到其子類;但是,在沿用其子類時,有時會進行一定配置的重寫,而此時重寫的配置就會變為一個不可控因素,產生意想不到的bug
示例:
筆者在使用時,設定了這樣的配置
<style name="ThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> <!--忽略部分程式碼--> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="buttonStyle">@style/EhiTheme.Button</item> </style> <!--主題修改為使用最新--> <style name="EhiTheme" parent="ThemeBase"> <!--定義v21 之前api的內容--> </style> <!--重寫button預設樣式--> <style name="EhiTheme.Button" parent="@style/Widget.AppCompat.Button"> <item name="android:background">@drawable/btn_default_ehi_selector</item> <item name="android:textColor">@color/white</item> </style>
程式碼可以看的出,此為一個button的預設樣式修改;而這份程式碼關鍵就在於
<item name="android:textColor">@color/white</item>
這句程式碼將按鈕文字設定為白色十分危險,因為Android中很多控制元件都會沿用這部分的樣式;但是,同時這個部分的樣式針對各個版本的Android系統上也是有著不同的體現!
以下是不同版本下,此重寫樣式後AlertDialog的不同體現

Android 8.0

Android 6.0
通過上圖可以看到,在Android 6.0的彈窗上,文字採用了colorAccent的顏色,而Android8.0則採用了文字繼承下來的白色;但是兩者在顯示上的一個共同點就是,背景都被置空;這就造成了白色背景加上白色字型的顯示bug!
a>同時,要特別注意的一點是,這樣的bug不止存在alertDialog,同樣會反映在 timePickDialog和datePickDialog上面!
b>另外,需要大家注意的是,在v7包下的alertDialog和app包下的alertDialog是讀取的兩套設定
v7包:
<item name="alertDialogTheme"></item>
app系統包下:
<item name="android:alertDialogTheme"></item>
如果專案在使用上不規範的情況下,很可能兩種dialog都會引入進行使用,那麼兩種其一不起作用,都會有產生Bug的風險!
解決方案:
針對Dialog的Theme主題部分,進行重寫
定義Alert自己的Theme:
colors.xml
<color name="colorAccent">#FFFF7E00</color>
Themes.xml
<style name="EhiTheme.AlertDialog" parent="Theme.AppCompat.Light.Dialog.Alert"> <item name="colorAccent">@color/colorAccent</item> <item name="android:textColor">@color/colorAccent</item> <item name="buttonStyle">@style/EhiTheme.Button.Alert</item> </style> <style name="EhiTheme.Button.Alert" parent="@style/Widget.AppCompat.Button"> <item name="android:background">@color/white</item> <item name="android:textColor">@color/colorAccent</item> </style> <style name="ThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> <!--定義v7 之後所有api的內容--> <!--基本主題色--> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <!--部分忽略--> <item name="buttonStyle">@style/EhiTheme.Button</item> <item name="alertDialogTheme">@style/EhiTheme.AlertDialog</item> <item name="android:alertDialogTheme">@style/EhiTheme.AlertDialog</item> </style>
v21中Themes.xml
<style name="EhiTheme" parent="ThemeBase"> <!--定義v21 之後api的內容--> <item name="android:timePickerDialogTheme">?alertDialogTheme</item> <item name="android:datePickerDialogTheme">?alertDialogTheme</item> <item name="android:alertDialogTheme">?alertDialogTheme</item> </style>
程式碼中,如果結構不好有多個baseActivity基類一定要注意!父類在繼承 FragmentActivity 和 AppCompatActivity 時,其展現形式是不一樣的,由於AppCompatActivity在為了保證變為統一樣式在內部做了很多封裝;(ps:theme繼承了的情況下 Theme.AppCompat.Light.NoActionBar ;使用基礎activity會crash),需要在新增後測試一下
針對view自定義屬性
自定義控制元件的屬性的命名的一點小建議:
以前我們的自定義view屬性的命名全憑喜好
主流的一般有這樣的,小駝峰的命名形式
<declare-styleable name="EhiTitleBar"> <attr name="isSearchView" format="boolean"/> <attr name="searchViewHint" format="string" /> <attr name="titleBackground" format="color|reference" /> </declare-styleable>
還有下劃線大法的
<declare-styleable name="EhiDrawingBoard"> <attr name="stroke_width" format="integer"/> <attr name="paint_color" format="color"/> <attr name="canvas_color" format="color|reference"/> <attr name="anti_alias" format="boolean"/> </declare-styleable>
但是我認為很多內容的使用,我們都應該更接近原生控制元件的使用:
例如textView
<TextView android:id="@+id/text_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:text="今天" android:layout_marginLeft="10dp" android:textColor="@color/black" android:textSize="18sp" />
可以看的出android的控制元件命名其實是分成兩部分
屬性是作用於其子類本身的內部,像這樣的文字尺寸
android:textSize="18sp"
採用了小駝峰法
而屬性比如是作用於控制元件的類似layoutparam這種和父類相關的屬性
android:layout_height="wrap_content"
以下劃線來區分,這樣其實我覺得對於學習和接受度其實都可以很快,畢竟在使用原始控制元件時的方式都類似
Style命名的一點小建議
style的使用命名規範的一點小建議
以前的style我們是這麼命名的:
<!-- 條目標籤樣式 --> <style name="item_reimburse_label"> <item name="android:layout_width">0dp</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_weight">0.3</item> </style>
但是,這種命名其實非常凌亂,
- 看不出style的層級關係,繼承關係
- 看不出它所屬的模組;所以在比較大的專案工程中,在上方使用者呼叫會非常混亂
- style的統一管理十分困難,有差不多的一組style有樣式改動,將會是一個災難
- 元件化後,各個模組容易出現命名重複問題
後來我們進行了一點優化
<!--myorder是module名--> <style name="myorder_item_reimburse_label"> <item name="android:layout_width">0dp</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_weight">0.3</item> </style>
命名雖然看的出模組所屬,但是我覺得沒有充分將style的功能發揮出來,使用也並不怎麼友好!
我認為比較適合我們在大型專案中工程化的style的命名是需要更向系統的style命名形式靠攏的:
一般我們可以採用大駝峰的命名形式,以 . 作為各種應用場景的區分
Module.頁面.控制元件型別.控制元件修飾描述
<!--總模組--> <style name="CompanyInfo" /> <!--總模組,所屬介面--> <style name="CompanyInfo.DriverMangerSearchOrderResult" /> <!--總模組,所屬介面.對應控制元件--> <style name="CompanyInfo.DriverMangerSearchOrderResult.ItemLabelContent"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:layout_marginTop">@dimen/tv_order_content_margin_top</item> <item name="android:ellipsize">end</item> </style> <!--總模組,所屬介面.對應控制元件.控制元件應用場景描述--> <style name="CompanyInfo.DriverMangerSearchOrderResult.ItemLabelContent.NoBackground"> <item name="android:background">@null</item> </style>
可是,我們很多情況下會有很多介面共用同樣的樣式,所以為了這種情況,我們可以利用 style的顯示繼承
首先,定義一個公共模組的樣式
<!--labelContentView的Style--> <style name="EhiBase.Widget.LabelContent"> <!--部分忽略--> <item name="contentColor">@color/colorGray100</item> <item name="contentHintColor">@color/colorGray350</item> </style>
然後,在自己的module裡面直接進行引用,這樣,在同時保證了命名的統一的同時,還兼顧的使用通用樣式
<!--用於個人管理模組--> <style name="PersonalManager" /> <!--個人資訊介面--> <style name="PersonalManager.MyInformation" /> <!--利用顯示繼承,使用通用樣式--> <style name="PersonalManager.MyInformation.LabelContent" parent="EhiBase.Widget.LabelContent"/>
這樣有以下優勢
- 有統一的模組描述,頁面描述,控制元件描述;
- 能夠更好使得名稱空間不衝突
- 同時利用 style的顯式繼承可以做到對控制元件的統一管控
- 類似Android原生的用法,上手更快
部分原始碼展示
<style name="Theme.AppCompat" parent="Base.Theme.AppCompat"/> <style name="Theme.AppCompat.CompactMenu" parent="Base.Theme.AppCompat.CompactMenu"/> <style name="Theme.AppCompat.DayNight" parent="Theme.AppCompat.Light"/> <style name="Theme.AppCompat.DayNight.DarkActionBar" parent="Theme.AppCompat.Light.DarkActionBar"/> <style name="Theme.AppCompat.DayNight.Dialog" parent="Theme.AppCompat.Light.Dialog"/> <style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert"/>
隱式繼承控制命名的完整性,顯式繼承控制模組與模組之間的通用部分