聲明:本文已授權微信公眾號Android程序員 (Android Trending) 在微信公眾號平臺原創首發。
年后重構了一版多主題框架,在重構過程中遇到了不少的坑,特此記錄下與君共勉。(Tips: 多主題框架也將于6月初開源啦.)
-
多彩主題和夜間主題
在寫多主題框架時,首先一個概念要分清就是多彩主題和夜間模式。- 多彩主題其實是白天模式的衍生,與夜間模式是對立的。
- 雖然夜間和多彩是對立,但還是建議多彩主題應該與夜間模式解偶,因為有時夜間模式的顏色變化并不是簡單的顏色取反,受產品設計的影響較大,有時甚至一個tag在夜間和多彩中的取色完全不一樣的,這時如果還在強求通過一次編碼“通吃“多彩和夜間,這樣的做法完全是不明智的,同時也會導致框架易用性變差。
當然如果某些控件在夜間模式下的需求只是簡單的顏色取反,對于這種情況,框架是應當給予適配支持的(不能一棒子打死嘛),因為這種特性支持很簡單,所以可以在基本不增加框架學習使用成本的前提下,大大減少程序員的重復編碼,提高了開發效率。 - 關于夜間模式的具體實現方式有很多,在這里推薦一篇文章 Android夜間模式最佳實踐,文中一共概述了三種實現方式,其中第三種通過修改uiMode來切換夜間模式 其實就是Google在support庫23.2.0版本(新增支持夜間模式,其實早就支持了0,0)中采用的方式,只不過在AppcompatDeleglate中進行了封裝,使用起來更加簡單了。
-
關于ColorDrawable
API21以下是不支持染色的,所以從兼容性上考慮,一般地對ColorDrawable直接new而不是染色。
源碼如下(API19):
/**
* Setting a color filter on a ColorDrawable has no effect.
*
* @param colorFilter Ignore.
*/
public void setColorFilter(ColorFilter colorFilter) {
}
- 關于GradientDrawable
比較特殊,API22以下是不支持直接tint的,這點在support庫中有很清楚的說明(DrawableCompatLollipop.java$setTintList):
```java
public static void setTintList(Drawable drawable, ColorStateList tint) {
if (drawable instanceof DrawableWrapperLollipop) {
// GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
// functionality instead
DrawableCompatBase.setTintList(drawable, tint);
} else {
// Else, we'll use the framework API
drawable.setTintList(tint);
}
}
另外值得注意的是,GradientDrawable不支持tint的原因有兩點,1).在API21以下它并沒有實現onStateChange方法,而onStateChange在view中的默認實現是直接返回false,所以它就不會隨著狀態的變化刷新UI了。2). 在API21的GradientDrawable源碼中并沒有支持setTint,這有點奇怪,因為其他Drawable基本都支持了,有時間要仔細對比下源碼。
protected boolean onStateChange(int[] state) {
return false;
}
-
關于setPressed(boolean)
setPressed 方法不同于setSelected方法,雖然它在執行過程中會更新Drawable的state狀態,但是不會調用invalidate函數(備注:并不是針對所有Drawable,stateDrawableList會在setPressed執行過程中調用invalidate())。
附上API23部分源碼://View.java public void setPressed(boolean pressed) { ... ... if (needsRefresh) { refreshDrawableState(); } ... ... } protected void drawableStateChanged() { ... ... final Drawable bg = mBackground; if (bg != null && bg.isStateful()) { bg.setState(state); } ... ... } //BitmapDrawable @Override protected boolean onStateChange(int[] stateSet) { ... ... mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); ... ... } //Drawable /** * Ensures the tint filter is consistent with the current tint color and * mode. */ PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint, PorterDuff.Mode tintMode) { ... ... if (tintFilter == null) { return new PorterDuffColorFilter(color, tintMode); } tintFilter.setColor(color); tintFilter.setMode(tintMode); return tintFilter; }
特別地,當一個textview設置了一張png為background并對該background設置了normaltint和pressed tint,然后你會發現background在按下時背景色并沒有tint。
解決的方法:1). 在view的drawstateChanged()中手動調用invalidate方法。2). 在view的drawstateChanged()中apply新的drawable state。3). 等待你來補充。 關于.9png
.9png在繪制時如果.9png內含有padding值,則5.0以下時view的padding會消失。如果想要view的padding保留,目前比較好的做法就是在set前先將view的padding值保存下來,然后等set之后再重新setPadding回去(首先要明確的一點是drawable和view的padding是有區別的)。關于StateListDrawable對child tint 無效
這是一個5.0以下的bug,現在比較好的解決方案就是繼承StateListDrawable,重寫它的selectDrawable方法,每次在狀態切換獲取對應的drawable時,手動進行setColorFilter設置。附上鏈接-
關于setButtonDrawable方法
setButtonDrawable方法在API21以下存在一個 非常隱蔽的bug。
在API21以下,如果CompoundButton已設置了一個buttonDrawable(非空),然后在調用setButtonDrawable(null),你會發現之前設置的buttonDrawable仍然存在!根本沒有被置空。
至于原因非常簡單,對比一下源碼就一目了然了。下面附上API23 和API19的相關源碼//API19 /** * Set the background to a given Drawable * * @param d The Drawable to use as the background */ public void setButtonDrawable(Drawable d) { if (d != null) { if (mButtonDrawable != null) { mButtonDrawable.setCallback(null); unscheduleDrawable(mButtonDrawable); } ... ... }
//API21 /** * Sets a drawable as the compound button image. * * @param drawable the drawable to set * @attr ref android.R.styleable#CompoundButton_button */ @Nullable public void setButtonDrawable(@Nullable Drawable drawable) { if (mButtonDrawable != drawable) { if (mButtonDrawable != null) { mButtonDrawable.setCallback(null); unscheduleDrawable(mButtonDrawable); } mButtonDrawable = drawable; if (drawable != null) { drawable.setCallback(this); ... ... } } }
-
關于obtain屬性
好吧,這個obtain屬性非常怪,有時候會出些莫名其妙的bug。- 在API21以下,如果在int [] ATTRS數組中將android屬性放在自定義屬性之后讀取,則你會發現android屬性的值將無法取到,-,-是不是很奇葩。
- 在API19上,如果將drawableLeft之類的android屬性放在一個int [] ATTRS中通過TypeArray讀取時,除了第一個android屬性能取到resourceId,之后的drawableXxx的resourceId解析的值都為0。
- 目前的解決方案是針對每個attr都單獨obtain一次,如果有更好的解決方案,歡迎支持。
拖沓了兩個月終于踩著五月份的尾巴把文章發了,唏噓...(拖延癥害死人--|||)
歡迎查看 個人博客.