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)
        }
    }
}

源码下载

相关推荐
2601_963771378 小时前
Offloading WP-Cron and Securing Ticket Webhooks on Enterprise IT Sites
android
花燃柳卧9 小时前
跨平台路由组件工程源码补充上传
android·flutter·kotlin
apihz9 小时前
随机驾考题目(C 照科一 / 科四 2000+ 题)免费API调用教程
android·java·c语言·开发语言·网络协议·tcp/ip
宸翰10 小时前
uni-app 设置 Android 底部虚拟导航栏背景色
android·前端·uni-app
TechNomad10 小时前
Kotlin类对象与接口详解
android·kotlin
QiLinkOS15 小时前
QiLink OS的失败数据共享平台的运作模式是否适用于所有行业?
android·开发语言·人工智能·算法·重构·开源
杉氧15 小时前
Framework 补完计划 (2):BufferQueue 与 SurfaceFlinger —— 像素的跨进程“接力赛”
android·架构·android jetpack
牢七15 小时前
RCE?复现成功
android
一化十15 小时前
Android16 自定义全局手势 任意界面依次点击屏幕四个角返回Home界面
android
三少爷的鞋17 小时前
Android 面试系列:Kotlin 协程的 delay 到底发生在哪个线程?
android