[譯]使用Android Theme屬性進(jìn)行個性化

原文地址——Styling Colors & Drawables w/ Theme Attributes诚啃。

你也許注意到

context.getResources().getColor(R.color.some_color_resource_id);

AndroidStudio會提示Resources#getColor(int)方法在Marshmallow版本已經(jīng)過時了益缎,可以使用Resources#getColor(int, Theme)來代替乎赴。
你也許知道最簡單的處理方法是調(diào)用:

ContextCompat.getColor(context, R.color.some_color_resource_id);

這個方法其實(shí)是下面方法的簡單寫法

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  return context.getResources().getColor(id, context.getTheme());
} else {
  return context.getResources().getColor(id);
}

很簡單,但它的內(nèi)部原理是什么入挣,為什么這個方法會過時递惋,為什么Theme參數(shù)之前不需要访惜?

Resources#getColor(int) & Resources#getColorStateList(int) 的問題

首先,讓我們來研究老方法內(nèi)部做了什么宙址。

代碼什么時候會報錯呢

為了明白方法過時的原因令野,考慮一下ColorStateList是使用以下xml文件來聲明的。當(dāng)這個xml應(yīng)用到TextView上時徽级,就使字體顏色指向了R.attr.colorAccentR.attr.colorPrimary的主題色气破。

<!-- res/colors/button_text_csl.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="?attr/colorAccent" android:state_enabled="false"/>
    <item android:color="?attr/colorPrimary"/>
</selector>

假設(shè)現(xiàn)在在代碼中獲取ColorStateList

ColorStateList csl = context.getResources().getColorStateList(R.color.button_text_csl);

出人意料的是,錯誤出現(xiàn)了餐抢!

W/Resources: ColorStateList color/button_text_csl has unresolved theme attributes!
             Consider using Resources.getColorStateList(int, Theme)
             or Context.getColorStateList(int)
        at android.content.res.Resources.getColorStateList(Resources.java:1011)
        ...
哪里出錯现使?

問題在于Resource并不與應(yīng)用中特定的Theme綁定,所以像R.attr.colorAccentR.attr.colorPrimary就無法從Theme中獲取到指定的顏色旷痕。事實(shí)上碳锈,直到API 23,才支持在ColorStateList中指定主題屬性欺抗,使用下面兩個方法可以實(shí)現(xiàn):

更方便的方法可以通過support library中的ResourcesCompat
ContextCompat
來獲取圾亏。

如何更優(yōu)雅地解決問題

v24.0AppCompt Support Library十拣,可以通過使用 AppCompatResources
來解決這個問題

ColorStateList csl = AppCompatResources.getColorStateList(context, R.color.button_text_csl);

Api23+AppCompat 會委托對應(yīng)的框架來實(shí)現(xiàn)志鹃,更早的版本會手動解析xml文件夭问,解析所有的主題屬性。如果還不夠曹铃,ColorStateList中的 android:alpha
已經(jīng)可以由低版本使用了(之前只能在API 23+上使用)

Resources#getDrawable(int)的問題

你猜對了缰趋。已過時的方法Resources#getDrawable(int)
Resources#getColor(int)Resources#getColorStateList(int)一樣有同樣的問題——直到API 21+xml文件中才支持主題屬性。所以铛只,如果你想支持Lolipop之前的版本埠胖,需要避免主題屬性或者動態(tài)構(gòu)造一個Drawable對象。

我不相信你淳玩,真的沒有例外直撤?

總是有例外的。
AppCompatResources類似蜕着, VectorDrawableCompat
AnimatedVectorDrawableCompat
可以解析主題屬性谋竖,例如你想把VectorDrawableCompat變成標(biāo)準(zhǔn)的灰色红柱,可以使用android:tint=?attr/colorControlNormal,在老版本上也可以使用蓖乘。

<vector 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24.0"
    android:viewportHeight="24.0"
    android:tint="?attr/colorControlNormal">

    <path
        android:pathData="..."
        android:fillColor="@android:color/white"/>
</vector>

原理在于锤悄,在support庫中解析xml中的主題屬性是使用[Theme#obtainStyledAttributes(AttributeSet, int[], int, int)
](http://developer.android.com/reference/android/content/res/Resources.Theme.html#obtainStyledAttributes(android.util.AttributeSet, int[], int, int)),很酷吧嘉抒。

突擊考試

我們用上面的知識做個簡單的測試零聚,有如下的ColorStateList

<!-- res/colors/button_text_csl.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="?attr/colorAccent" android:state_enabled="false"/>
    <item android:color="?attr/colorPrimary"/>
</selector>

假設(shè)聲明主題如下:

<!-- res/values/themes.xml -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/vanillared500</item>
    <item name="colorPrimaryDark">@color/vanillared700</item>
    <item name="colorAccent">@color/googgreen500</item>
</style>

<style name="CustomButtonTheme" parent="ThemeOverlay.AppCompat.Light">
    <item name="colorPrimary">@color/brown500</item>
    <item name="colorAccent">@color/yellow900</item>
</style>

最后假設(shè)你使用上述方法解析主題屬性動態(tài)構(gòu)造ColorStateList

@ColorInt
private static int getThemeAttrColor(Context context, @AttrRes int colorAttr) {
  TypedArray array = context.obtainStyledAttributes(null, new int[]{colorAttr});
  try {
    return array.getColor(0, 0);
  } finally {
    array.recycle();
  }
}

private static ColorStateList createColorStateList(Context context) {
  return new ColorStateList(
      new int[][]{
          new int[]{-android.R.attr.state_enabled}, // Disabled state.
          StateSet.WILD_CARD,                       // Enabled state.
      },
      new int[]{
          getThemeAttrColor(context, R.attr.colorAccent),  // Disabled state.
          getThemeAttrColor(context, R.attr.colorPrimary), // Enabled state.
      });
}

試試預(yù)測在API 23API 19下按鈕enabledisable的外觀(例如,在#5和#8中些侍,按鈕使用了android:theme="@style/CustomButtonTheme"來獲取自定義主題)隶症。

Resources res = ctx.getResources();

// (1)
int deprecatedTextColor = res.getColor(R.color.button_text_csl);
button1.setTextColor(deprecatedTextColor);

// (2)
ColorStateList deprecatedTextCsl = res.getColorStateList(R.color.button_text_csl);
button2.setTextColor(deprecatedTextCsl);

// (3)
int textColorXml = 
    AppCompatResources.getColorStateList(ctx, R.color.button_text_csl).getDefaultColor();
button3.setTextColor(textColorXml);

// (4)
ColorStateList textCslXml = AppCompatResources.getColorStateList(ctx, R.color.button_text_csl);
button4.setTextColor(textCslXml);

// (5)
Context themedCtx = button5.getContext();
ColorStateList textCslXmlWithCustomTheme =
    AppCompatResources.getColorStateList(themedCtx, R.color.button_text_csl);
button5.setTextColor(textCslXmlWithCustomTheme);

// (6)
int textColorJava = getThemeAttrColor(ctx, R.attr.colorPrimary);
button6.setTextColor(textColorJava);

// (7)
ColorStateList textCslJava = createColorStateList(ctx);
button7.setTextColor(textCslJava);

// (8)
Context themedCtx = button8.getContext();
ColorStateList textCslJavaWithCustomTheme = createColorStateList(themedCtx);
button8.setTextColor(textCslJavaWithCustomTheme);
答案
API 19的設(shè)備
API 23的設(shè)備

注意在截屏中的粉色并不奇怪,而是當(dāng)未指定主題時岗宣,默認(rèn)加載默認(rèn)主題的效果蚂会。

與往常一樣,感謝閱讀耗式。如果有任何問題請?jiān)u論胁住,Github源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刊咳,一起剝皮案震驚了整個濱河市彪见,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芦缰,老刑警劉巖企巢,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枫慷,死亡現(xiàn)場離奇詭異让蕾,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)或听,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門探孝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人誉裆,你說我怎么就攤上這事顿颅。” “怎么了足丢?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵粱腻,是天一觀的道長。 經(jīng)常有香客問我斩跌,道長绍些,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任耀鸦,我火速辦了婚禮柬批,結(jié)果婚禮上啸澡,老公的妹妹穿的比我還像新娘。我一直安慰自己氮帐,他們只是感情好嗅虏,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著上沐,像睡著了一般皮服。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上参咙,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天冰更,我揣著相機(jī)與錄音,去河邊找鬼昂勒。 笑死蜀细,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戈盈。 我是一名探鬼主播奠衔,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼塘娶!你這毒婦竟也來了归斤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤刁岸,失蹤者是張志新(化名)和其女友劉穎脏里,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虹曙,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡迫横,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了酝碳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矾踱。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖疏哗,靈堂內(nèi)的尸體忽然破棺而出呛讲,到底是詐尸還是另有隱情,我是刑警寧澤返奉,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布贝搁,位于F島的核電站,受9級特大地震影響芽偏,放射性物質(zhì)發(fā)生泄漏雷逆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一哮针、第九天 我趴在偏房一處隱蔽的房頂上張望关面。 院中可真熱鬧坦袍,春花似錦、人聲如沸等太。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缩抡。三九已至奠宜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瞻想,已是汗流浹背压真。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蘑险,地道東北人滴肿。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像佃迄,于是被迫代替她去往敵國和親泼差。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容