前陣子看了bilibili上的一些技術相關影片,碼牛學院的公開課程
Android动态加载技术的高级进阶,手写实现网易云主题换肤框架
影片中的講者用Java初步實作了一個修改主題的框架
學習的過程動作做是個滿重要的階段
因此我把影片中的這個框架跟著教學手刻一次
刻完套用在我自己的測試專案上時發現還有些問題
接著就依照遇到的問題進行進一步的修改
在教學影片中看得到的邏輯規則,本篇文章就不贅述,我會直接說明我基於教學的框架增加或修改的部分
不想浪費時間看文章可以直接看我的Repository ChangeThemeSample
例如這樣統一設定TextView的風格
<style name="style_button_text">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:textColor">@color/white</item>
<item name="android:layout_gravity">center</item>
<item name="android:gravity">center</item>
<item name="android:background">@drawable/selector_btn_circle_default</item>
<item name="android:paddingBottom">10dp</item>
</style>
<TextView
style="@style/style_button_text" />
在使用style的情況下,透過AttributeSet getAttributeName只會取到"style"
而style_button_text裡面有設定的textColor, background是取不到的
這樣在後續設定主題時,使用style的寫法就會失效
因此需要針對style的況狀更深入的處理
首先我定義了,方便foreach確認屬性是否存在
private static final int[] NATIVE_ATTRIBUTE_ID = {
android.R.attr.textColor,
android.R.attr.background,
android.R.attr.src
};
逐一確認style中是否能夠取到我們對應的屬性
TypedArray typedArray = view.getContext().obtainStyledAttributes(attrs.getStyleAttribute(), NATIVE_ATTRIBUTE_ID);
if (typedArray.length() > 0){
for(int ti = 0; ti < typedArray.length(); ti++){
try{
if(typedArray.hasValue(ti)){
int resId = typedArray.getResourceId(ti, -1);
if(resId != -1) {
addSkinItem(view, NATIVE_ATTRIBUTE_NAME[ti], resId, skinItems);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
typedArray.recycle();
這邊有遇到一個問題還沒深入去找原因,NATIVE_ATTRIBUTE_ID中將textColor及background順序調換後,textColor會失效取不到resourceId,之後有空必須要查查
解決方案當然就是要把物件及相應的屬性也加入到判斷的清單
private static final String[] THIRDPARTY_ATTRIBUTE_NAME = {
"tabIndicator",
"tabIndicatorColor"
};
private static final String[] THIRDPARTY_VIEW = {
"com.google.android.material.tabs.TabLayout"
};
利用反射的方式將數值置換
Method setSelectedTabIndicator = view.getClass().getDeclaredMethod("setSelectedTabIndicator", Drawable.class);
setSelectedTabIndicator.invoke(view, SkinManager.getInstance().getDrawable(skinItem.getResId()));
Android的物件、屬性千百種,實在不太可能每一個都加入我們的判斷規則中,就算都整理進來多少也會有效能上的問題,例如每個onCreateView都要foreach跑一次包含2000項目的array大概受不了
因此需要增加一個可以讓人擴充判斷及設定的功能
這邊以ProgressBar及progressDrawable屬性作為範例
首先需要生成一個callback(CustomViewAttributeApplyListener),這個callback會將當下要置換主題的view、屬性及目前取到的資源回傳,開發人員收到此回傳實再進行相對應的設定
這邊比較需要注意點的點是,進行設定要取得資源時必須透過SkinManager中的getDrawable、getColor相關方法取得,如果直接使用當下的conetext取回資源則會取到原始APK的資源,而非主題包
CustomViewAttributeApplyListener listener = (view, fieldName, resId) -> {
String viewName = view.getClass().getSimpleName();
switch (viewName){
case "ProgressBar":
((ProgressBar)view.findViewById(R.id.progressBar)).setProgressDrawable(SkinManager.getInstance().getDrawable(resId));
break;
}
};
SkinManager.getInstance().addCustomView(new SkinCustomView(ProgressBar.class.getSimpleName(), ProgressBar.class.getName(), new String[]{"progressDrawable"}, listener));
大概是每個專案都一定會遇到的狀況,某些UI上效果需要依照API回傳的狀態呈現
例如狀態1文字使用顏色A、背景使用AA;狀態2文字使用顏色B、背景使用BB
這個狀況再設定時就需要參考問題三
同樣是使用SkinManager中的getDrawable、getColor相關方法取得資源
個人認為看完公開課程還算滿有收穫的,也算是有個現成的可以學習,也講解得滿詳細的,後面進階課銷售相關的話真的很多。不過也可以透過他講述的一些狀況了解內地的行情可能也不是壞事
這個套件肯定還有許多需要優化的地方,以及Bug需要修改,有任何建議歡迎提出或加入一起維護