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

源码下载

相关推荐
studyForMokey3 小时前
kotlin 函数类型接口lambda写法
android·开发语言·kotlin
梁同学与Android7 小时前
Android --- 新电脑安装Android Studio 使用 Android 内置模拟器电脑直接卡死,鼠标和键盘都操作不了
android·ide·android studio
山雨楼9 小时前
ExoPlayer架构详解与源码分析(14)——ProgressiveMediaPeriod
android·架构·音视频·源码·exoplayer·media3
IsaacBan11 小时前
XJBX-6-Android启动App进程
android
DoubleYellowIce11 小时前
Android Studio阅读frameworks源码的正确姿势
android·android studio
分享者花花11 小时前
最佳 iPhone 解锁软件工具,可免费下载用于电脑操作的
android·windows·macos·ios·pdf·word·iphone
小菜琳16 小时前
Android显式启动activity和隐式启动activity分别都是怎么启动?请举例说明二者使用时的注意事项。
android
许进进16 小时前
FlutterWeb渲染模式及提速
android·flutter·web
helson赵子健17 小时前
Rust 在 Android 中的应用
android·架构·rust
2401_8523867117 小时前
苹果ios安卓apk应用APP文件怎么修改手机APP显示的名称
android·智能手机