Android 換膚技術(shù)資料整理
Android換膚技術(shù)總結(jié)
背景
縱觀現(xiàn)在各種Android app,其換膚需求可以歸為
- 白天/黑夜主題切換(或者別的名字,通常2套),如同花順/自選股/天天動(dòng)聽(tīng)等,UI表現(xiàn)為一個(gè)switcher。
- 多種主題切換,通常為會(huì)員特權(quán),如QQ/QQ空間。
對(duì)于第一種來(lái)說(shuō),目測(cè)應(yīng)該是直接通過(guò)本地theme來(lái)做的,即所有圖片/顏色的資源都在apk里面打包了。
而對(duì)于第二種,則相對(duì)復(fù)雜一些,由于作為一種線(xiàn)上服務(wù),可能上架新皮膚,且那么多皮膚包放在apk里面實(shí)在太占體積了,所以皮膚資源會(huì)在選擇后再進(jìn)行下載,也就不能直接使用android的那套theme。
技術(shù)方案
內(nèi)部資源加載方案和動(dòng)態(tài)下載資源下載兩種。
動(dòng)態(tài)下載可以稱(chēng)為一種黑科技了,因?yàn)橥枰猦ack系統(tǒng)的一些方法,所以在部分機(jī)型和新的API上有時(shí)候可能有坑,但相對(duì)好處則很多
- 圖片/色值等資源由于是后臺(tái)下發(fā)的,可以隨時(shí)更新
- APK體積減小
- 對(duì)應(yīng)用開(kāi)發(fā)者來(lái)說(shuō),換膚幾乎是透明的,不需要關(guān)心有幾套皮膚
- 可以作為增值服務(wù)賣(mài)錢(qián)!!
內(nèi)部資源加載方案
內(nèi)部資源加載都是通過(guò)android本身那套theme來(lái)做的,相對(duì)業(yè)務(wù)開(kāi)發(fā)來(lái)說(shuō)工作量更大(需要定義attr和theme),不同方案類(lèi)似地都是在BaseActivity里面做setTheme,差別主要在解決以下2個(gè)問(wèn)題的策略:
- setTheme后如何實(shí)時(shí)刷新,而不用重新創(chuàng)建頁(yè)面(尤其是listview里面的item)。
- 哪些view需要刷新,刷新什么(背景?字體顏色?ImageView的src?)。
自定義view
做自定義view是為了在setTheme后會(huì)去立即刷新,更新頁(yè)面UI對(duì)應(yīng)資源(如TextView替換背景圖和文字顏色),在上述項(xiàng)目中,則是通過(guò)對(duì)rootView進(jìn)行遍歷,對(duì)所有實(shí)現(xiàn)了ColorUiInterface的view/viewgroup進(jìn)行setTheme操作來(lái)實(shí)現(xiàn)即使刷新的。
顯然這樣太重了,需要把應(yīng)用內(nèi)的各種view/viewgroup進(jìn)行替換。
手動(dòng)綁定view和要改變的資源類(lèi)型
這個(gè)…我們看看用法吧….
ViewGroupSetter listViewSetter = new ViewGroupSetter(mNewsListView); // 綁定ListView的Item View中的news_title視圖,在換膚時(shí)修改它的text_color屬性 listViewSetter.childViewTextColor(R.id.news_title, R.attr.text_color); // 構(gòu)建Colorful對(duì)象來(lái)綁定View與屬性的對(duì)象關(guān)系 mColorful = new Colorful.Builder(this) .backgroundDrawable(R.id.root_view, R.attr.root_view_bg) // 設(shè)置view的背景圖片 .backgroundColor(R.id.change_btn, R.attr.btn_bg) // 設(shè)置背景色 .textColor(R.id.textview, R.attr.text_color) .setter(listViewSetter) // 手動(dòng)設(shè)置setter .create(); // 設(shè)置文本顏色
我就是想換個(gè)皮膚,還得在activity里自己去設(shè)置要改變哪個(gè)view的什么屬性,對(duì)應(yīng)哪個(gè)attribute?是不是成本太高了?而且activity的邏輯也很容易被弄得亂七八糟。
動(dòng)態(tài)資源加載方案
resource替換
覆蓋application的getResource方法,實(shí)現(xiàn)自己的resource,優(yōu)先加載本地皮膚包文件夾下的資源包,對(duì)于性能問(wèn)題,可以通過(guò)attribute或者資源名稱(chēng)規(guī)范(如需要換膚則用skin_開(kāi)頭)來(lái)優(yōu)化,從而不對(duì)不換膚的資源進(jìn)行額外檢查開(kāi)銷(xiāo)。
不過(guò)由于Android5.1源碼里,drawable初始化的時(shí)候調(diào)用的是loadDrawable,而不是resource.getDrawable,而loadDrawable是私有的方法,無(wú)法覆蓋,所以雖然很方便,卻無(wú)法繼續(xù)使用(不用關(guān)心任何皮膚相關(guān)的事情,android:color指定顏色就行了,神奇滴會(huì)自動(dòng)換膚)。
自定義LayoutInflator.Factory
開(kāi)源項(xiàng)目可參照Android-Skin-Loader。
即setFactory使用自定義的LayoutInflator.Factory,可以重點(diǎn)關(guān)注該項(xiàng)目中的SkinInflaterFactory和SkinManager(實(shí)現(xiàn)了自己的getColor、getDrawable、getBitmap、getColorStateList等等方法)。
需要自定義一個(gè)tag比如app:customStyle,重寫(xiě)所有的style,轉(zhuǎn)成set方法,這樣帶來(lái)的犧牲就是增加了換膚的成本,要寫(xiě)很多style,自己去set,并不完全透明了。
Hack Resources internally
黑科技方法,直接對(duì)Resources進(jìn)行hack,Resources.Java:
// Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all // single-threaded, and after that these are immutable. private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables = new LongSparseArray<Drawable.ConstantState>(); private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists = new LongSparseArray<ColorStateList>();
直接對(duì)Resources里面的這三個(gè)LongSparseArray進(jìn)行替換,由于apk運(yùn)行時(shí)的資源都是從這三個(gè)數(shù)組里面加載的,所以只要采用interceptor模式:
public class DrawablePreloadInterceptor extends LongSparseArray<Drawable.ConstantState>
自己實(shí)現(xiàn)一個(gè)LongSparseArray,并通過(guò)反射set回去,就能實(shí)現(xiàn)換膚,具體getDrawable等方法里是怎么取preload數(shù)組的,可以自己看Resources的源碼。
等等,就這么簡(jiǎn)單?,NONO,少年你太天真了,怎么去加載xml,9patch的padding怎么更新,怎么打包/加載自定義的皮膚包,drawable的狀態(tài)怎么刷新,等等。這些都是你需要考慮的,在存在插件的app中,還需要考慮是否會(huì)互相覆蓋resource id的問(wèn)題,進(jìn)而需要修改apt,把resource id按位放在2個(gè)range。
手Q和獨(dú)立版QQ空間使用的是這種方案,效果挺好。
總結(jié)
盡管動(dòng)態(tài)加載方案比較黑科技,可能因?yàn)橄到y(tǒng)API的更改而出問(wèn)題,但相對(duì)來(lái)說(shuō)
好處有
- 靈活性高,后臺(tái)可以隨時(shí)更新皮膚包
- 相對(duì)透明,開(kāi)發(fā)者幾乎不用關(guān)心有幾套皮膚,不用去定義各種theme和attr,甚至連皮膚包的打包都可以交給設(shè)計(jì)或者專(zhuān)門(mén)的同學(xué)
- apk體積節(jié)省
存在的問(wèn)題
沒(méi)有完善的開(kāi)源項(xiàng)目,如果我們采用動(dòng)態(tài)加載的第二種方案,需要的項(xiàng)目功能包括:
- 自定義皮膚包結(jié)構(gòu)
- 換膚引擎,加載皮膚包資源并load,實(shí)時(shí)刷新。
- 皮膚包打包工具
- 對(duì)各種rom的兼容
如果有這么一個(gè)項(xiàng)目的話(huà),就一勞永逸了,有興趣的同學(xué)可以聯(lián)系一下,大家一起搞一搞。
內(nèi)部加載方案大同小異,主要解決的都是即時(shí)刷新的問(wèn)題,然而從目前的一些開(kāi)源項(xiàng)目來(lái)看,仍然沒(méi)有特別簡(jiǎn)便的方案。讓我選的話(huà),我寧愿讓界面重新創(chuàng)建,比如重啟activity,或者remove所有view再添加回來(lái)(或者你可能想遍歷rootview,然后一個(gè)個(gè)檢查是否需要換膚然后set…)。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
- Android實(shí)現(xiàn)apk插件方式換膚的實(shí)例講解
- Android開(kāi)發(fā)實(shí)現(xiàn)切換主題及換膚功能示例
- android使用SkinManager實(shí)現(xiàn)換膚功能的示例
- android換膚功能 如何動(dòng)態(tài)獲取控件中背景圖片的資源id?
- Android應(yīng)用開(kāi)發(fā)中實(shí)現(xiàn)apk皮膚文件換膚的思路分析
- 分析Android App中內(nèi)置換膚功能的實(shí)現(xiàn)方式
- Android編程實(shí)現(xiàn)換膚功能實(shí)例
- Android實(shí)現(xiàn)換膚的兩種思路分析
- 基于Android-Skin-Loader實(shí)現(xiàn)換膚效果
相關(guān)文章
Android使用AsyncQueryHandler實(shí)現(xiàn)獲取手機(jī)聯(lián)系人功能
這篇文章主要為大家詳細(xì)介紹了Android使用AsyncQueryHandler實(shí)現(xiàn)獲取手機(jī)聯(lián)系人功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Android 判斷是開(kāi)發(fā)debug模式,還是發(fā)布release模式的方法
下面小編就為大家?guī)?lái)一篇Android 判斷是開(kāi)發(fā)debug模式,還是發(fā)布release模式的方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-12-12Android studio利用gradle打jar包并混淆的方法詳解
昨天準(zhǔn)備把寫(xiě)好的代碼使用gradle打jar包出來(lái),并打算加混淆。打jar包容易,結(jié)果在混淆上走了彎路。所以這篇文章主要介紹了關(guān)于Android studio利用gradle打jar包并混淆的方法,需要的朋友可以參考下。2017-03-03Android實(shí)現(xiàn)懸浮可拖拽的Button
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)懸浮可拖拽的Button,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06IOS開(kāi)發(fā)向右滑動(dòng)返回前一個(gè)頁(yè)面功能(demo)
本文給大家介紹使用android實(shí)現(xiàn)向右滑動(dòng)返回一個(gè)頁(yè)面的功能,大家都知道在ios7中,蘋(píng)果的原生態(tài)應(yīng)用幾乎都能夠通過(guò)向右滑動(dòng)來(lái)返回到前一個(gè)頁(yè)面,這樣可以避免用戶(hù)在單手操作時(shí)用大拇指去點(diǎn)擊那個(gè)遙遠(yuǎn)的返回鍵,下面小編就給帶來(lái)了實(shí)現(xiàn)代碼,有需要的朋友可以參考下2016-06-06Android App開(kāi)發(fā)中ViewPager組件的入門(mén)使用教程
這篇文章主要介紹了Android App開(kāi)發(fā)中ViewPager組件的入門(mén)使用教程,ViewPager主要用來(lái)實(shí)現(xiàn)通過(guò)滑動(dòng)來(lái)切換頁(yè)面的效果,需要的朋友可以參考下2016-03-03Android編程滑動(dòng)效果之Gallery+GridView實(shí)現(xiàn)圖片預(yù)覽功能(附demo源碼下載)
這篇文章主要介紹了Android編程滑動(dòng)效果之Gallery+GridView實(shí)現(xiàn)圖片預(yù)覽功能,結(jié)合實(shí)例形式分析了Android通過(guò)GridView和Gallery兩個(gè)控件模仿Gallery圖像集圖片預(yù)覽功能,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-02-02Android 啟動(dòng)另一個(gè)App/apk中的Activity實(shí)現(xiàn)代碼
這篇文章主要介紹了Android 啟動(dòng)另一個(gè)App/apk中的Activity實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04Android中TextView自動(dòng)適配文本大小的幾種解決方案
在布局中使用的話(huà),注意按照你最大的設(shè)備來(lái)設(shè)置字體大小,這樣在小設(shè)備上回自動(dòng)縮放,下面這篇文章主要給大家介紹了關(guān)于Android中TextView自動(dòng)適配文本大小的幾種解決方案,需要的朋友可以參考下2022-06-06