我的又一個神奇的框架——Skins換膚框架

為什么會有換膚的需求

app的換膚,可以降低app用戶的審美疲勞。再好的UI設計,一直不變的話,也會對用戶體驗大打折扣,即使表面上不說,但心里或多或少會有些難受。所以app的界面要適當的改版啊,要不然可難受死用戶了,特別是UI設計還相對較丑的。

換膚是什么

換膚是將app的背景色、文字顏色以及資源圖片,一鍵進行全部切換的過程。這里就包括了圖片資源和顏色資源。

Skins怎么使用

Skins就是一個解決這樣一種換膚需求的框架。

// 添加以下代碼到項目根目錄下的build.gradle
allprojects {repositories {maven { url "https://jitpack.io" }}
}
// 添加以下代碼到app模塊的build.gradle
dependencies {// skins依賴了dora框架,所以你也要implementation doraimplementation("com.github.dora4:dora:1.1.12")implementation 'com.github.dora4:dview-skins:1.4'
}

我以更換皮膚顏色為例,打開res/colors.xml。

<!-- 需要換膚的顏色 -->
<color name="skin_theme_color">@color/cyan</color>
<color name="skin_theme_color_red">#d23c3e</color>
<color name="skin_theme_color_orange">#ff8400</color>
<color name="skin_theme_color_black">#161616</color>
<color name="skin_theme_color_green">#009944</color>
<color name="skin_theme_color_blue">#0284e9</color>
<color name="skin_theme_color_cyan">@color/cyan</color>
<color name="skin_theme_color_purple">#8c00d6</color>

將所有需要換膚的顏色,添加skin_前綴和_skinname后綴,不加后綴的就是默認皮膚。
然后在啟動頁應用預設的皮膚類型。在布局layout文件中使用默認皮膚的資源名稱,像這里就是R.color.skin_theme_color,框架會自動幫你替換。要想讓框架自動幫你替換,你需要讓所有要換膚的Activity繼承BaseSkinActivity。

private fun applySkin() {val manager = PreferencesManager(this)when (manager.getSkinType()) {0 -> {}1 -> {SkinManager.changeSkin("cyan")}2 -> {SkinManager.changeSkin("orange")}3 -> {SkinManager.changeSkin("black")}4 -> {SkinManager.changeSkin("green")}5 -> {SkinManager.changeSkin("red")}6 -> {SkinManager.changeSkin("blue")}7 -> {SkinManager.changeSkin("purple")}}
}

另外還有一個情況是在代碼中使用換膚,那么跟布局文件中定義是有一些區別的。

val skinThemeColor = SkinManager.getLoader().getColor("skin_theme_color")

這個skinThemeColor拿到的就是當前皮膚下的真正的skin_theme_color顏色,比如R.color.skin_theme_color_orange的顏色值“#ff8400”或R.id.skin_theme_color_blue的顏色值“#0284e9”。
SkinLoader還提供了更簡潔設置View顏色的方法。

override fun setImageDrawable(imageView: ImageView, resName: String) {val drawable = getDrawable(resName) ?: returnimageView.setImageDrawable(drawable)
}override fun setBackgroundDrawable(view: View, resName: String) {val drawable = getDrawable(resName) ?: returnview.background = drawable
}override fun setBackgroundColor(view: View, resName: String) {val color = getColor(resName)view.setBackgroundColor(color)
}

框架原理解析

先看BaseSkinActivity的源碼。

package dora.skin.baseimport android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import android.view.InflateException
import android.view.LayoutInflater
import android.view.View
import androidx.collection.ArrayMap
import androidx.core.view.LayoutInflaterCompat
import androidx.core.view.LayoutInflaterFactory
import androidx.databinding.ViewDataBinding
import dora.BaseActivity
import dora.skin.SkinManager
import dora.skin.attr.SkinAttr
import dora.skin.attr.SkinAttrSupport
import dora.skin.attr.SkinView
import dora.skin.listener.ISkinChangeListener
import dora.util.LogUtils
import dora.util.ReflectionUtils
import java.lang.reflect.Constructor
import java.lang.reflect.Method
import java.util.*abstract class BaseSkinActivity<T : ViewDataBinding> : BaseActivity<T>(),ISkinChangeListener, LayoutInflaterFactory {private val constructorArgs = arrayOfNulls<Any>(2)override fun onCreateView(parent: View?, name: String, context: Context, attrs: AttributeSet): View? {if (createViewMethod == null) {val methodOnCreateView = ReflectionUtils.findMethod(delegate.javaClass, false,"createView", *createViewSignature)createViewMethod = methodOnCreateView}var view: View? = ReflectionUtils.invokeMethod(delegate, createViewMethod, parent, name,context, attrs) as View?if (view == null) {view = createViewFromTag(context, name, attrs)}val skinAttrList = SkinAttrSupport.getSkinAttrs(attrs, context)if (skinAttrList.isEmpty()) {return view}injectSkin(view, skinAttrList)return view}private fun injectSkin(view: View?, skinAttrList: MutableList<SkinAttr>) {if (skinAttrList.isNotEmpty()) {var skinViews = SkinManager.getSkinViews(this)if (skinViews == null) {skinViews = arrayListOf()}skinViews.add(SkinView(view, skinAttrList))SkinManager.addSkinView(this, skinViews)if (SkinManager.needChangeSkin()) {SkinManager.apply(this)}}}private fun createViewFromTag(context: Context, viewName: String, attrs: AttributeSet): View? {var name = viewNameif (name == "view") {name = attrs.getAttributeValue(null, "class")}return try {constructorArgs[0] = contextconstructorArgs[1] = attrsif (-1 == name.indexOf('.')) {// try the android.widget prefix first...createView(context, name, "android.widget.")} else {createView(context, name, null)}} catch (e: Exception) {// We do not want to catch these, lets return null and let the actual LayoutInflaternull} finally {// Don't retain references on context.constructorArgs[0] = nullconstructorArgs[1] = null}}@Throws(InflateException::class)private fun createView(context: Context, name: String, prefix: String?): View? {var constructor = constructorMap[name]return try {if (constructor == null) {// Class not found in the cache, see if it's real, and try to add itval clazz = context.classLoader.loadClass(if (prefix != null) prefix + name else name).asSubclass(View::class.java)constructor = clazz.getConstructor(*constructorSignature)constructorMap[name] = constructor}constructor!!.isAccessible = trueconstructor.newInstance(*constructorArgs)} catch (e: Exception) {// We do not want to catch these, lets return null and let the actual LayoutInflaternull}}override fun onCreate(savedInstanceState: Bundle?) {val layoutInflater = LayoutInflater.from(this)LayoutInflaterCompat.setFactory(layoutInflater, this)super.onCreate(savedInstanceState)SkinManager.addListener(this)}override fun onDestroy() {super.onDestroy()SkinManager.removeListener(this)}override fun onSkinChanged(suffix: String) {SkinManager.apply(this)}companion object {val constructorSignature = arrayOf(Context::class.java, AttributeSet::class.java)private val constructorMap: MutableMap<String, Constructor<out View>> = ArrayMap()private var createViewMethod: Method? = nullval createViewSignature = arrayOf(View::class.java, String::class.java,Context::class.java, AttributeSet::class.java)}
}

我們可以看到BaseSkinActivity繼承自dora.BaseActivity,所以dora框架是必須要依賴的。有人說,那我不用dora框架的功能,可不可以不依賴dora框架?我的回答是,不建議。Skins對Dora生命周期注入特性采用的是,依賴即配置。

package dora.lifecycle.applicationimport android.app.Application
import android.content.Context
import dora.skin.SkinManagerclass SkinsAppLifecycle : ApplicationLifecycleCallbacks {override fun attachBaseContext(base: Context) {}override fun onCreate(application: Application) {SkinManager.init(application)}override fun onTerminate(application: Application) {}
}

所以你無需手動配置<meta-data android:name="dora.lifecycle.config.SkinsGlobalConfig" android:value="GlobalConfig"/>,Skins已經自動幫你配置好了。那么我順便問個問題,BaseSkinActivity中最關鍵的一行代碼是哪行?LayoutInflaterCompat.setFactory(layoutInflater, this)這行代碼是整個換膚流程最關鍵的一行代碼。我們來干預一下所有Activity onCreateView時的布局加載過程。我們在SkinAttrSupport.getSkinAttrs中自己解析了AttributeSet。

    /*** 從xml的屬性集合中獲取皮膚相關的屬性。*/fun getSkinAttrs(attrs: AttributeSet, context: Context): MutableList<SkinAttr> {val skinAttrs: MutableList<SkinAttr> = ArrayList()var skinAttr: SkinAttrfor (i in 0 until attrs.attributeCount) {val attrName = attrs.getAttributeName(i)val attrValue = attrs.getAttributeValue(i)val attrType = getSupportAttrType(attrName) ?: continueif (attrValue.startsWith("@")) {val ref = attrValue.substring(1)if (TextUtils.isEqualTo(ref, "null")) {// 跳過@nullcontinue}val id = ref.toInt()// 獲取資源id的實體名稱val entryName = context.resources.getResourceEntryName(id)if (entryName.startsWith(SkinConfig.ATTR_PREFIX)) {skinAttr = SkinAttr(attrType, entryName)skinAttrs.add(skinAttr)}}}return skinAttrs}

我們只干預skin_開頭的資源的加載過程,所以解析得到我們需要的屬性,最后得到SkinAttr的列表返回。

package dora.skin.attrimport android.view.View
import android.widget.ImageView
import android.widget.TextView
import dora.skin.SkinLoader
import dora.skin.SkinManagerenum class SkinAttrType(var attrType: String) {/*** 背景屬性。*/BACKGROUND("background") {override fun apply(view: View, resName: String) {val drawable = loader.getDrawable(resName)if (drawable != null) {view.setBackgroundDrawable(drawable)} else {val color = loader.getColor(resName)view.setBackgroundColor(color)}}},/*** 字體顏色。*/TEXT_COLOR("textColor") {override fun apply(view: View, resName: String) {val colorStateList = loader.getColorStateList(resName) ?: return(view as TextView).setTextColor(colorStateList)}},/*** 圖片資源。*/SRC("src") {override fun apply(view: View, resName: String) {if (view is ImageView) {val drawable = loader.getDrawable(resName) ?: returnview.setImageDrawable(drawable)}}};abstract fun apply(view: View, resName: String)/*** 獲取資源管理器。*/val loader: SkinLoaderget() = SkinManager.getLoader()
}

當前skins框架只定義了幾種主要的換膚屬性,你理解原理后,也可以自己進行擴展,比如RadioButton的button屬性等。

開源項目傳送門

如果你要深入理解完整的換膚流程,請閱讀skins的源代碼,[https://github.com/dora4/dview-skins] 。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/18508.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/18508.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/18508.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android Surface對應的Buffer怎么傳遞給HWC

Android Surface對應的Buffer怎么傳遞給HWC 引言 因為要預研Android Video overlay&#xff0c;需要將SurfaceView對應的GraphicBuffer從drm_hwcomposer中剝離出來&#xff0c;這就需要們了解SurfaceView對應的GraphicBuffer的前世今生&#xff0c;以及它的數據流向以及在各個…

輕兔推薦 —— vfox

簡介 vfox 是一個跨平臺且可擴展的版本管理工具&#xff0c;終于有一個可以管理所有運行環境的工具了 - 支持一鍵安裝 Java、Node.js、Flutter、.Net、Golang、PHP、Python等多種環境 - 支持一鍵切換不同版本 特點 支持Windows(非WSL)、Linux、macOS! 支持不同項目不同版本、…

(四)事件系統

視頻鏈接:尚硅谷2024最新版微信小程序 文章目錄 事件綁定和事件對象事件分類以及阻止事件冒泡事件傳參-data-*自定義數據事件傳參-mark 自定義數據事件綁定和事件對象 小程序中綁定事件與在網頁開發中綁定事件幾乎一致,只不過在小程序不能通過 on 的方式綁定事件,也沒有 cli…

C# 9.0的init訪問器

不控制可變性 下面是我們最常見的屬性聲明方式&#xff0c;允許屬性在類的內部和外部都可以讀取和修改 public int Id { get; set; }namespace Demo {public class Company{public int Id { get; set; }public Company(){}public Company(int id){Id id; // 可以在構造函數中…

22.Volatile原理

文章目錄 Volatile原理1.Volatile語義中的內存屏障1.1.volatile寫操作的內存屏障1.1.1.StoreStore 屏障1.1.2.StoreLoad 屏障 1.2.volatile讀操作的內存屏障1.2.1.LoadStore屏障1.2.2.LoadLoad屏障 2.volatile不具備原子性2.1.原理 Volatile原理 1.Volatile語義中的內存屏障 在…

用于生成 Avatar 的文本引導式情感和運動控制-InstructAvatar

網址 https://wangyuchi369.github.io/InstructAvatar/ 用于生成 Avatar 的文本引導式情感和運動控制 官網翻譯 最近的會說話的頭像生成模型在實現與音頻的真實和準確的嘴唇同步方面取得了長足的進步&#xff0c;但在控制和傳達頭像的詳細表情和情感方面往往存在不足&#…

APM2.8如何做加速度校準

加速度的校準建議準備一個六面平整&#xff0c;邊角整齊的方形硬紙盒或者塑料盒&#xff0c;如下圖所示&#xff0c;我們將以它作為APM校準時的水平垂直姿態參考&#xff0c;另外當然還需要一塊水平的桌面或者地面 首先用雙面泡沫膠或者螺絲將APM主板正面向上固定于方形盒子上&…

JavaScrip原型對象

參考 JavaScrip原型對象 | LogDicthttps://www.logdict.com/archives/javascripyuan-xing-mo-shi

每天寫兩道(二)LRU緩存、

146.LRU 緩存 . - 力扣&#xff08;LeetCode&#xff09; 請你設計并實現一個滿足 LRU (最近最少使用) 緩存 約束的數據結構。 實現 LRUCache 類&#xff1a; LRUCache(int capacity) 以 正整數 作為容量 capacity 初始化 LRU 緩存int get(int key) 如果關鍵字 key 存在于緩存…

如何使用Python和大模型進行數據分析和文本生成

如何使用Python和大模型進行數據分析和文本生成 Python語言以其簡潔和強大的特性&#xff0c;成為了數據科學、機器學習和人工智能開發的首選語言之一。隨著大模型&#xff08;Large Language Models, LLMs&#xff09;如GPT-4的崛起&#xff0c;我們能夠利用這些模型實現諸多…

Revit——(2)模型的編輯、軸網和標高

目錄 一、關閉縮小的隱藏窗口 二、標高&#xff08;可創建平面&#xff0c;其他標高線復制即可&#xff09; 三、軸網 周圍的四個圈和三角表示四個里面&#xff0c;可以移動&#xff0c;不要刪除 一、關閉縮小的隱藏窗口 二、標高&#xff08;可創建平面&#xff0c;其他標…

計算機體系結構期末快速復習

文章目錄 前言CPI&#xff0c;MIPS&#xff08;大題1&#xff09;加速比&#xff08;大題2&#xff09;流水線&#xff08;大題3&#xff09;CRAY-1向量機&#xff08;大題4&#xff09;Tomasulo算法&#xff08;大題5&#xff09;概念簡答題計算機系統結構的經典定義什么是透明…

深入分析 Android Activity (二)

文章目錄 深入分析 Android Activity (二)1. Activity 的啟動模式&#xff08;Launch Modes&#xff09;1.1 標準模式&#xff08;standard&#xff09;1.2 單頂模式&#xff08;singleTop&#xff09;1.3 單任務模式&#xff08;singleTask&#xff09;1.4 單實例模式&#xf…

利用邊緣計算網關的工業設備數據采集方案探討-天拓四方

隨著工業4.0時代的到來&#xff0c;工業設備數據采集成為了實現智能制造、提升生產效率的關鍵環節。傳統的數據采集方案往往依賴于中心化的數據處理方式&#xff0c;但這種方式在面對海量數據、實時性要求高的工業場景時&#xff0c;往往顯得力不從心。因此&#xff0c;利用邊緣…

CSS實現一個雨滴滑落效果

使用純CSS來實現一個真實的雨滴滑落效果可能會有些挑戰&#xff0c;因為CSS主要關注于靜態樣式和簡單的動畫效果。然而&#xff0c;你可以使用CSS動畫和keyframes來模擬一個雨滴滑落的簡化效果。 以下是一個基本的示例&#xff0c;展示如何使用CSS來模擬雨滴從頂部滑落到底部的…

AI學習指南數學工具篇-MATLAB中的凸優化工具

AI學習指南數學工具篇-MATLAB中的凸優化工具 在人工智能領域&#xff0c;凸優化是一個非常重要的數學工具&#xff0c;它在機器學習、深度學習、數據分析等領域都有著廣泛的應用。而MATLAB作為一款強大的數學工具軟件&#xff0c;提供了豐富的凸優化工具和函數&#xff0c;為用…

二叉樹的鏈式結構(二叉樹)與順序結構(堆)---數據結構

一、樹的概念與結構 1、樹的概念 樹是一種非線性的數據結構&#xff0c;它是由n&#xff08;n>0&#xff09;個有限結點組成一個具有層次關系的集合。我們常把它叫做樹&#xff0c;是因為它看起來像一棵倒掛的樹&#xff0c;它的根是朝上的&#xff0c;而葉是朝下的。 下面…

給我一個用斷言結果執行下一步的例子

在使用 pytest 和 Selenium 進行自動化測試時&#xff0c;通常我們會根據斷言的結果來決定測試流程的走向。如果斷言失敗&#xff0c;測試通常會停止執行后續的步驟&#xff0c;因為失敗意味著被測系統沒有按照預期工作。然而&#xff0c;有時候我們可能需要在斷言失敗后執行特…

每日復盤-20240528

今日重點關注&#xff1a; 20240528 六日漲幅最大: ------1--------300956--------- 英力股份 五日漲幅最大: ------1--------301361--------- 眾智科技 四日漲幅最大: ------1--------301361--------- 眾智科技 三日漲幅最大: ------1--------301361--------- 眾智科技 二日漲…

前端編程語言——JS背景知識、JS基礎語法、算數運算符和關系運算符(1)

0、前言&#xff1a; JS全稱是JavaScript&#xff0c;是一種腳本語言&#xff0c;誕生于1995年&#xff0c;JS是由ECMAScript&#xff08;包含js語法&#xff09;、BOM&#xff08;Brower Oject Model&#xff0c;和瀏覽器相關操作&#xff09;、DOM&#xff08;Document Obje…