針對自定義及動態(tài)創(chuàng)建View的換膚實踐

一、背景

公司的業(yè)務需要使用換膚功能實現(xiàn)白天/黑夜模式,調研了市場主流換膚框架,主要采用了LayoutInflater.Factory接口干涉Xml中View解析的過程,將創(chuàng)建View的過程由自己來接手。但本項目大量使用自定義View及動態(tài)創(chuàng)建View,Xml中描述界面的情況不多,針對這種情況,我設計了一套輕量級的實時換膚框架。

二、使用

項目UI框架是單Activity+多Fragment的結構,為滿足實時刷新,不出現(xiàn)頁面閃爍的需求,所以從三個方面來實現(xiàn)換膚功能。

1.Activity

當前項目中需要實時刷新的Activity只有首頁和設置頁,所以單獨對這兩個Activity進行處理。首先需要實現(xiàn)Skinable接口,在頁面創(chuàng)建和銷毀時添加監(jiān)聽,這樣,主題發(fā)生改變時,就會通知到applySkin()方法。

class MainActivity : RootActivity(), MainContract.View, Skinable {
    ...
    private fun initView() {
        SkinManager.instance.register(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        SkinManager.instance.unregister(this)
    }

    override fun applySkin() {
        container.setBackgroundColor(resources.getColor(R.color.bg_color_primary))
        navigation.setBackgroundColor(resources.getColor(R.color.bg_color_primary))
        for (fragment in supportFragmentManager.fragments) {
            if (fragment is RootFragment && fragment.isAdded && !fragment.isDetached) {
                fragment.refreshStatusBar()
                if (fragment is Skinable) {
                    fragment.applySkin()
                }
            }
        }
        changeStatusBarTheme()
    }
    ...
}

2.Fragment

Fragment是UI界面的承載體,所以在RootFragment中實現(xiàn)了Skinable接口,所有的Fragment需要覆寫applySkin()方法,在里面處理自己的換膚邏輯,即設置頁面控件的顏色屬性(如背景色、字體顏色、圖標等)。

3.View

View是每個頁面最基礎的元素,出于方便使用和易維護的角度,對這層當中的自定義控件和系統(tǒng)控件做了區(qū)別處理。

系統(tǒng)控件

直接在Fragment的applySkin()里調用設置相關屬性的方法。

showQrCode.background = resources.getDrawable(R.drawable.selector_with_ripple)
addEnigma.setTextColor(resources.getColor(R.color.text_color_tips))

自定義控件

對于自定義控件,直接讓控件實現(xiàn)Skinable接口,在自定義控件內部處理控件自己的換膚邏輯,這樣外部不用考慮內部控件的邏輯,只需在 Fragment的applySkin()方法中直接調用該自定義控件的applySkin()方法。

class PasswordEditText: AppCompatEditText, Skinable {
    ...
    override fun applySkin() {
        mTextPaint.color = resources.getColor(R.color.text_color_input_hint)
        background = resources.getDrawable(R.drawable.selector_login_edit_bkg)
        setTextColor(resources.getColor(R.color.text_color_minor))
        setHintTextColor(resources.getColor(R.color.text_color_input_hint))
    }
    ...
}

4.資源定義

這里拿黑夜模式進行舉例

添加資源目錄

首先需要在build.gralde文件的android節(jié)點中添加res-nigit。

android {
    ...
    sourceSets {
        main {
            res.srcDirs = ['src/main/res', 'src/main/res-night']
        }
        ...
    }
}

定義資源名

黑夜模式的資源需要在res_night中加入同名的_night后綴,如果未添加,默認會取白天模式的。

drawable

colors.xml也需要這樣定義。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary_night">#333333</color>
    <color name="colorPrimaryDark_night">#00574B</color>
    <color name="colorAccent_night">#26B36D</color>

    <!-- text colors -->
    <color name="text_color_primary_night">#ffffff</color>
    <color name="text_color_minor_night">#cccccc</color>
    <color name="text_color_tips_night">#909399</color>
    ...

    <!-- background colors -->
    <color name="bg_color_primary_night">#333333</color>>
    <color name="bg_color_pressed_night">#2d2d2d</color>
    ...

    <!-- default avatar colors -->
    <color name="default_avatar_red_night">#ED4E4E</color>
    <color name="default_avatar_blue_night">#6C9DD9</color>
    ...

    <!-- bottom sheet colors -->
    <color name="colorSheetText_night">#DE000000</color>
    <color name="colorSheetTitle_night">#8A000000</color>
    <color name="colorSheetDivider_night">#3f717171</color>
    <color name="bg_color_status_bar_night">#333333</color>
</resources>

在這里,推薦大家如果使用圖標,最好用SVG圖,除了占用空間小、縮放無質量損失以外,添加對于黑夜模式的時候,也只需要修改SVG文件色值即可達到。

三、原理

其實核心思想上面也提到了,就是繼承Resource,覆寫了getColor()getDrawable()方法。

class CustomResources(val resources: Resources) :
    Resources(resources.assets, resources.displayMetrics, resources.configuration) {

    override fun getColor(id: Int): Int {
        return SkinManager.instance.getColor(id)
    }

    override fun getDrawable(id: Int): Drawable {
        return SkinManager.instance.getDrawable(id)
    }

    fun updateConfig(config: Configuration?, metrics: DisplayMetrics?) {
        resources.updateConfiguration(config, metrics)
    }
}

然后在SkinManager中通過SkinResources獲取相應主題的資源。

class SkinResources {
    ...
    fun getSkinColor(context: Context, id:Int): Int {
        val resources = context.resources
        val type = resources.getResourceTypeName(id)
        val color = resources.getResourceEntryName(id)
        val identifier = getIdentifier(context, nameConvert(color), type)
        return when {
            identifier != 0 -> resources.getColor(identifier)
            else -> resources.getColor(id)
        }
    }

    fun getSkinDrawable(context:Context,id:Int): Drawable {
        val resources = context.resources
        val type = resources.getResourceTypeName(id)
        val drawable = resources.getResourceEntryName(id)
        val identifier = getIdentifier(context, nameConvert(drawable), type)
        return when {
            identifier != 0 -> resources.getDrawable(identifier)
            else ->resources.getDrawable(id)
        }
    }
    ...
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容