Android 换肤之主题换肤

文章目录

Android 换肤之主题换肤

概述

Android 实现应用内换肤的常用方式(两种):

  • 通过Theme切换主题,即静态方法。
  • 通过AssetManager切换主题,可实现动态切换。

效果

浅色模式:

深色模式:

实现

代码结构

定义属性

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--    描述信息-->
    <attr name="description" format="string" />
    <!--    描述文本颜色-->
    <attr name="descriptionColor" format="color" />
    <!--    头像-->
    <attr name="portrait" format="reference" />
    <!--    头像颜色-->
    <attr name="portraitColor" format="color" />
    <!--    头像边框-->
    <attr name="portraitBorder" format="reference" />
    <!--    背景图片-->
    <attr name="bgImage" format="reference" />
    <!--    背景颜色-->
    <attr name="bgColor" format="color" />
</resources>

定义主题

xml 复制代码
<style name="LightTheme" parent="AppTheme">
    <item name="description">@string/light_description</item>
    <item name="descriptionColor">@color/light_text_color</item>
    <item name="portrait">@drawable/ic_light</item>
    <item name="portraitBorder">@drawable/shape_light_border</item>
    <item name="portraitColor">@color/black</item>
    <item name="bgImage">@drawable/light_bg_drawable</item>
    <item name="bgColor">@color/light_bg_color</item>
</style>

<style name="NightTheme" parent="AppTheme">
    <item name="description">@string/night_description</item>
    <item name="descriptionColor">@color/night_text_color</item>
    <item name="portrait">@drawable/ic_night</item>
    <item name="portraitBorder">@drawable/shape_night_border</item>
    <item name="portraitColor">@color/white</item>
    <item name="bgImage">@drawable/night_bg_drawable</item>
    <item name="bgColor">@color/night_bg_color</item>
</style>

在Activity中使用

kotlin 复制代码
open class BaseActivity : AppCompatActivity, ThemeObserver {

    constructor() : super()

    constructor(@LayoutRes layoutId: Int) : super(layoutId)

    override fun onCreate(savedInstanceState: Bundle?) {
        ThemeManager.setTheme(this)
        super.onCreate(savedInstanceState)
        ThemeManager.addObserver(this)
    }

    override fun onThemeChanged() {
        ThemeManager.setTheme(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        ThemeManager.removeObserver(this)
    }
}
kotlin 复制代码
class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.e("TAG", "MainActivity onCreate()")
        initViews()
    }

    private fun initViews() {
        btnLight.setOnClickListener {
            ThemeManager.setTheme(this, THEME_LIGHT)
        }
        btnNight.setOnClickListener {
            ThemeManager.setTheme(this, THEME_NIGHT)
        }
        btnToSecond.setOnClickListener {
            startActivity(Intent(this, SecondActivity::class.java))
        }
    }

    /**
     * 更新控件
     */
    override fun onThemeChanged() {
        super.onThemeChanged()
        Log.e("TAG", "MainActivity onThemeChanged")
        val helper = ResourceHelper(this)
        helper.setBackgroundResource(root, R.attr.bgColor)
        helper.setImageResource(bgImage, R.attr.bgImage)
        helper.setImageResource(portrait, R.attr.portrait)
        helper.setImageColorResource(portrait, R.attr.portraitColor)
        helper.setBackgroundResource(portrait, R.attr.portraitBorder)
        helper.setTextResource(description, R.attr.description)
        helper.setTextColorResource(description, R.attr.descriptionColor)
    }
}

在Fragment中使用

kotlin 复制代码
abstract class BaseFragment : Fragment, ThemeObserver {

    constructor() : super()

    constructor(@LayoutRes layoutId: Int) : super(layoutId)

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        ThemeManager.addObserver(this)
        return super.onCreateView(inflater, container, savedInstanceState)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        ThemeManager.removeObserver(this)
    }
}
kotlin 复制代码
class ThemeFragment : BaseFragment(R.layout.fragment_theme) {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        view.btnLight.setOnClickListener {
            ThemeManager.setTheme(activity!!, THEME_LIGHT)
        }
        view.btnNight.setOnClickListener {
            ThemeManager.setTheme(activity!!, THEME_NIGHT)
        }
    }

    /**
     * 更新控件
     */
    override fun onThemeChanged() {
        Log.e("TAG", "ThemeFragment onThemeChanged")
        if (isAdded) {
            val helper = ResourceHelper(context!!)
            helper.setBackgroundResource(root, R.attr.bgColor)
            helper.setImageResource(bgImage, R.attr.bgImage)
            helper.setImageResource(portrait, R.attr.portrait)
            helper.setImageColorResource(portrait, R.attr.portraitColor)
            helper.setBackgroundResource(portrait, R.attr.portraitBorder)
            helper.setTextResource(description, R.attr.description)
            helper.setTextColorResource(description, R.attr.descriptionColor)
        }
    }
}

工具类

kotlin 复制代码
object ThemeManager {
    const val THEME_LIGHT = 1
    const val THEME_NIGHT = 2

    fun setTheme(activity: Activity, themeType: Int) {
        val sp = activity.getSharedPreferences("app", AppCompatActivity.MODE_PRIVATE)
        sp.edit()
            .putInt("themeType", themeType)
            .commit()
        setTheme(activity)
        notifyThemeChanged();
    }

    fun setTheme(activity: Activity) {
        val sp = activity.getSharedPreferences("app", AppCompatActivity.MODE_PRIVATE)
        val themeType = sp.getInt("themeType", THEME_LIGHT)
        var themeId = -1
        if (themeType == THEME_LIGHT) {
            themeId = R.style.LightTheme
        } else if (themeType == THEME_NIGHT) {
            themeId = R.style.NightTheme
        }
        activity.setTheme(themeId)
    }

    private val mThemeObservers = mutableListOf<ThemeObserver>()

    fun addObserver(observer: ThemeObserver) {
        mThemeObservers.add(observer)
    }

    fun removeObserver(observer: ThemeObserver) {
        mThemeObservers.remove(observer)
    }

    fun notifyThemeChanged() {
        for (i in mThemeObservers.size - 1 downTo 0) {
            mThemeObservers[i].onThemeChanged()
        }
    }
}
kotlin 复制代码
public interface ThemeObserver {

    fun onThemeChanged()
}
kotlin 复制代码
class ResourceHelper(val context: Context) {

    /**
     * 获取当前主题
     */
    fun getTheme(): Resources.Theme {
        return context.theme
    }

    /**
     * 设置背景资源
     */
    fun setBackgroundResource(view: View, attr: Int) {
        val theme: Resources.Theme = getTheme()
        val typedValue = TypedValue()
        if (theme.resolveAttribute(attr, typedValue, true)) {
            val resId = typedValue.resourceId
            view.setBackgroundResource(resId)
        }
    }

    /**
     * 设置图片资源
     */
    fun setImageResource(imageView: ImageView, attr: Int) {
        val theme: Resources.Theme = getTheme()
        val typedValue = TypedValue()
        if (theme.resolveAttribute(attr, typedValue, true)) {
            val resId = typedValue.resourceId
            imageView.setImageResource(resId)
        }
    }

    /**
     * 设置图片颜色
     */
    fun setImageColorResource(imageView: ImageView, attr: Int) {
        val drawable: Drawable? = imageView.drawable
        drawable?.let {
            val tintDrawable = DrawableCompat.wrap(it).mutate()
            val theme: Resources.Theme = getTheme()
            val typedValue = TypedValue()
            if (theme.resolveAttribute(attr, typedValue, true)) {
                val resId = typedValue.resourceId
                val color = ResourcesCompat.getColor(context.resources, resId, theme)
                DrawableCompat.setTint(tintDrawable, color)
                imageView.setImageDrawable(tintDrawable)
            }
        }
    }

    /**
     * 设置文本资源
     */
    fun setTextResource(textView: TextView, attr: Int) {
        val theme: Resources.Theme = getTheme()
        val typedValue = TypedValue()
        if (theme.resolveAttribute(attr, typedValue, true)) {
            val resId = typedValue.resourceId
            textView.setText(resId)
        }
    }

    /**
     * 设置文本颜色资源
     */
    fun setTextColorResource(textView: TextView, attr: Int) {
        val theme: Resources.Theme = getTheme()
        val typedValue = TypedValue()
        if (theme.resolveAttribute(attr, typedValue, true)) {
            val resId = typedValue.resourceId
            val color = ResourcesCompat.getColor(context.resources, resId, theme)
            textView.setTextColor(color)
        }
    }
}

源码下载

相关推荐
dpxiaolong27 分钟前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
tangweiguo030519871 小时前
Android 混合开发实战:统一 View 与 Compose 的浅色/深色主题方案
android
老狼孩111222 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发
打死不学Java代码2 小时前
PaginationInnerInterceptor使用(Mybatis-plus分页)
android·java·mybatis
IT乐手4 小时前
android 解决系统级应用 WebView 加载崩溃的问题
android
Kapaseker4 小时前
Kotlin泛型精解:类型世界的奇幻之旅
android·kotlin
顾林海4 小时前
深入探究 Android Native 代码的崩溃捕获机制
android·面试·性能优化
爱分享的程序员5 小时前
前端跨端框架的开发以及IOS和安卓的开发流程和打包上架的详细流程
android·前端·ios
thigh_d5 小时前
uniapp 安卓离线本地打包,Android Studio生成apk包
android·uni-app·android studio
二J7 小时前
管理100个小程序-很难吗
android·小程序