Android 换肤之主题换肤

news2024/12/21 18:46:46

文章目录

  • Android 换肤之主题换肤
    • 概述
    • 效果
    • 实现
      • 代码结构
      • 定义属性
      • 定义主题
      • 在Activity中使用
      • 在Fragment中使用
      • 工具类
    • 源码下载

Android 换肤之主题换肤

概述

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

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

效果

浅色模式:

在这里插入图片描述

深色模式:

在这里插入图片描述

实现

代码结构

在这里插入图片描述

定义属性

<?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>

定义主题

<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中使用

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)
    }
}
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中使用

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

工具类

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()
        }
    }
}
public interface ThemeObserver {

    fun onThemeChanged()
}
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)
        }
    }
}

源码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1888039.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《企业实战分享 · 常用运维中间件》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; 近期刚转战 CSDN&#xff0c;会严格把控文章质量&#xff0c;绝不滥竽充数&#xff0c;如需交流&#xff…

【第三版 系统集成项目管理工程师】第5 章 软件工程

持续更新。。。。。。。。。。。。。。。 【第三版】第五章 软件工程 5.1软件工程定义练习 5.2软件需求5.2.1雾求的层次1.业务需求-P2032.用户需求-P2033.系统需求-P203 5.2.2质量功能部署 P2035.2.3需求获取 P2045.2.4需求分析1.结构化分析-P2042.面向对象分析-P207 5.2.5号求…

IDEA中Maven的配置

目录 1. 安装maven 2. 配置环境变量 3. IDEA中配置Maven 4. 配置仓库目录 1. 安装maven 官网下载地址&#xff1a;Maven – Download Apache Maven 下载后&#xff0c;将zip压缩包解压到某个目录即可。 2. 配置环境变量 变量名称随意&#xff0c;通常为M2_HOME&#xff…

DLS平台:运价持续上涨,未来航运市场何去何从?

摘要&#xff1a; 近期&#xff0c;上海出口集装箱结算运价指数&#xff08;SCFIS&#xff09;欧洲航线连续10周上涨&#xff0c;涨幅高达151%。随着多家航运公司宣布涨价&#xff0c;市场供应紧张导致运价居高不下。本文将详细分析当前运价上涨的原因、航运市场的变化及未来运…

精打细算:建设酒吧或小酒馆

开设一家酒吧或小酒馆&#xff0c;不仅仅是一场对美酒的浪漫追求&#xff0c;更是一次深思熟虑的投资决策过程。在这个过程中&#xff0c;选择合适的酿造设备是至关重要的一步&#xff0c;它直接关系到酒吧的运营效率、产品品质乃至最终的盈利能力。本文天泰小编将从多个维度出…

『Z-MeetUP』 6月29日线下活动——跨越Web3基础设施边界

Meetup In Hangzhou ZJUBCA 2024 求是 创新 TIME:2024/06/29 ADD:浙江大学紫金港校区 概述 / OVERVIEW 悠闲午后&#xff0c;感谢大家积极参与此次社区聚会&#xff0c;我们共同探讨了跨越Web3基础设施边界&#xff0c;发掘技术前沿&#xff0c;领略链上生态的无限可能。 让我…

Qt Creator13配置Android开发环境

QT Creator13是目前&#xff08;2024年&#xff09;最新版本&#xff0c;配置Android开发环境有一些不一样&#xff0c;走了一些弯路&#xff0c;记录如下。 1、安装JDK和SDK 下载安装JDK和SDK&#xff0c;建议安装在无空格和中文字符的目录下。 具体安装步骤不再赘述&#…

TongRDS2214手动部署版指引(by lqw+sy)

文章目录 前言准备工作单机版集群版哨兵版多个中心节点配置 前言 由于一些特殊原因&#xff08;例如服务器没有联网&#xff0c;没有办法直接更新和下载unzip指令&#xff0c;从而导致控制台版本安装节点之后&#xff0c;会报file not found的错误&#xff0c;或者使用不了rds…

CQ 社区版2.13.3 | 支持全局开启OTP登录、文本导入功能可独立控制……

又到一月一度的 CloudQuery 发版时间啦&#xff01; 本次版本更新&#xff0c;对多个模块进行了功能的优化和完善&#xff0c;比如将文本导入与 insert 权限脱离使文本导入可单独控制&#xff1b;将工具权限与权限等级脱离&#xff0c;使其能独立授权和提权&#xff1b;操作模…

项目实战--Spring Boot + Minio文件切片上传下载

1.搭建环境 引入项目依赖 <!-- 操作minio的java客户端--> <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.2</version> </dependency> <!-- jwt鉴权相应依赖--> &…

初试总分409分,专业课143,西电821专业

非常感谢自己考研409分上岸西安电子科技大学&#xff0c;杭州研究院&#xff0c;专业课143分&#xff0c;跟的研梦&#xff0c;讲课以及答疑还是非常专业的。 821专业课课本总共有四本&#xff0c;都在官网考纲的参考书里写了&#xff0c;不过主要参考其中两本&#xff0c;一本…

机器学习笔记 LightGBM:理解算法背后的数学原理

一、简述 在一次数据科学的比赛中&#xff0c;我有机会使用 LightGBM&#xff0c;这是一种最先进的机器学习算法&#xff0c;它极大地改变了我们处理预测建模任务的方式。我对它在数千个数据点上进行训练的速度感到着迷&#xff0c;同时保持了其他算法难以达到的准确性。LightG…

EasyExcel4导入导出数据(基于MyBatisPlus)

一、POM依赖 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><m…

OK527N-C开发板-简单的性能测试

OK527N-C CoreMark 获取CoreMark源码 首先使用Git克隆仓库&#xff1a; git clone https://github.com/eembc/coremark.git cd coremark修改Makefile 首先复制文件夹 cp -rf posix ok527之后修改ok527文件夹下的core_portme.mak文件&#xff0c;将CC修改如下 CC aarch6…

uniapp地图点击获取位置

主页面 <view class"right-content" click.stop"kilometer(item)"><view class"km">{{item.distance||0}}km</view><image src"../../static/map.png" mode""style"width: 32rpx; height: 32rpx…

模块电源(九):DC-DC工作模式和调制方法

DC-DC 需要在一定的开关频率下工作&#xff0c;并需要一个控制电路配合来稳定 DCDC 的输出电压。因此&#xff0c;针对不同的控制方案&#xff0c;需要设计不同的 DC-DC 工作模式和调制方法来使其稳定工作。 一、工作模式 一个 DC-DC 电路中&#xff0c;开关管导通阶段的电感电…

【前端】HTML+CSS复习记录【5】

文章目录 前言一、padding、margin、border&#xff08;边框边距&#xff09;二、样式优先级三、var&#xff08;使用 CSS 变量更改多个元素样式&#xff09;四、media quary&#xff08;媒体查询&#xff09;系列文章目录 前言 长时间未使用HTML编程&#xff0c;前端知识感觉…

【高中数学/基本不等式】已知a,b皆为正实数,且a+b=2 求:1/a+4/b的最小值?(2011年重庆理科卷第七题)

【题目】 已知a,b皆为正实数&#xff0c;且ab2 求&#xff1a;1/a4/b的最小值&#xff1f; 【解答】 解法一&#xff1a;基本不等式法 由ab2可推知a/2b/21 1/a4/b(a/2b/2)/a(2a2b)/b1/2b/2a2a/b22.5b/2a2a/b >2.52倍根号下&#xff08;b/2a*2a/b&#xff09;2.52*14.…

力扣双指针算法题目:双数之和,三数之和,四数之和

目录 一&#xff1a;双数之和 1.题目&#xff1a; 2.思路解析 3.代码 二&#xff1a;三数之和 1.题目 2.思路解析 3&#xff0c;代码 三&#xff1a;四数字之和 1.题目 2.思路解析 3.代码 一&#xff1a;双数之和 1.题目&#xff1a; 输入一个递增排序的数组和一…