Android开发之实现无重建无重启activity完成当前页面的主题切换

在工作中实现Android的主题切换非常简单:仅需一行代码实现

Kotlin 复制代码
//设置app为夜间模式
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)

但是上面方法仅限普通场景,如果我当前页面有unity相关的模块,这样切换主题会直接导致app奔溃。原因是这样切换会先销毁activity在重建。如果activity销毁时是选哦干掉当前进程确保进程确认不存在否则会引发unity和Android之间的各种问题。因为再含有unity页面的activity如果要实现换肤上面的通用方法会导致闪崩。

Kotlin 复制代码
在activity的销毁方法中需要执行unity的销毁方法在执行杀掉档期那进程的方法避免进程残留
UnityKit.destroy()
Process.killProcess(Process.myPid())

那么说到关键点了。如何解决呢?在以前车载项目中我们app项目中和fw底层项目中都用到了换肤,警告总结发现思路出奇的一致,就是通过viewtree遍历然后重新设置主题达到重载最新主题的方式可以解决此问题。

解决方法:

步骤一:在清单文件配置当前activity换肤时不销毁不重建。最重要的就是下面这行

Kotlin 复制代码
android:configChanges="uiMode"
Kotlin 复制代码
  <activity
            android:name=".MainActivity"
            android:configChanges="uiMode"
            android:exported="true"
            android:screenOrientation="portrait">
            <!--other-->
        </activity>

步骤二:然后在activity中重写配置改变的方法

Kotlin 复制代码
   override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        // 检查是否为夜间模式变更
        val nightMode = newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK
        if (nightMode == Configuration.UI_MODE_NIGHT_YES) {
            LogUtils.d("切换到深色模式")
            // 切换到深色主题
            reloadActivityTheme()
        } else {
            LogUtils.d("切换到浅色模式")
            // 切换到浅色主题
            reloadActivityTheme()
        }
    }

步骤三:如上代码在每次换肤成功时调用viewtree遍历工具类设置最新的主题即可。本人提供一个遍历所有viewtree的工具类如下

Kotlin 复制代码
package com.xiayiye5.kit


import android.app.Activity
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment

/**
 * Android View遍历和主题重载工具类
 * 提供遍历Activity和Fragment中所有View并重新应用主题的方法
 */


/**
 * 遍历Activity中所有View并重新应用主题
 * @param Activity 目标Activity
 * @param themeResId 主题资源ID 可不传暂时用不到
 */
fun Activity.reloadActivityTheme(themeResId: Int = 0) {
    val rootView = findViewById<View>(android.R.id.content)
    traverseAndApplyTheme(rootView, themeResId)
}

/**
 * 遍历Fragment中所有View并重新应用主题
 * @param Fragment 目标Fragment
 * @param themeResId 主题资源ID
 */
fun Fragment.reloadFragmentTheme(themeResId: Int) {
    val view = view
    if (view != null) {
        traverseAndApplyTheme(view, themeResId)
    }
}

/**
 * 递归遍历View树并应用主题
 * @param view 当前View
 * @param themeResId 主题资源ID
 */
private fun traverseAndApplyTheme(view: View, themeResId: Int) {
    // 应用主题到当前View
    applyThemeToView(view, themeResId)
    // 如果是ViewGroup,递归处理子View
    if (view is ViewGroup) {
        for (i in 0 until view.childCount) {
            traverseAndApplyTheme(view.getChildAt(i), themeResId)
        }
    }
}

/**
 * 应用主题到单个View
 * @param view 目标View
 * @param themeResId 主题资源ID
 */
private fun applyThemeToView(view: View, themeResId: Int) {
    try {
        // 获取主题中的颜色属性
        val typedValue = TypedValue()
        val context = view.context
        val theme = context.theme
        // 应用背景色
        if (theme.resolveAttribute(android.R.attr.windowBackground, typedValue, true)) {
            view.setBackgroundColor(typedValue.data)
        }
        // 应用文本颜色(如果是TextView)
        if (view is android.widget.TextView) {
            if (theme.resolveAttribute(android.R.attr.textColor, typedValue, true)) {
                view.setTextColor(typedValue.data)
            }
        }

    } catch (e: Exception) {
        // 处理异常情况
        e.printStackTrace()
    }
}

/**
 * 获取Activity中所有View的列表
 * @param activity 目标Activity
 * @return View列表
 */
fun getAllViews(activity: AppCompatActivity): List<View> {
    val viewList = mutableListOf<View>()
    val rootView = activity.findViewById<View>(android.R.id.content)
    collectViews(rootView, viewList)
    return viewList
}

/**
 * 获取Fragment中所有View的列表
 * @param fragment 目标Fragment
 * @return View列表
 */
fun getAllViews(fragment: Fragment): List<View> {
    val viewList = mutableListOf<View>()
    val rootView = fragment.view
    if (rootView != null) {
        collectViews(rootView, viewList)
    }
    return viewList
}

/**
 * 收集View树中的所有View
 * @param view 当前View
 * @param viewList 存储View的列表
 */
private fun collectViews(view: View, viewList: MutableList<View>) {
    viewList.add(view)
    if (view is ViewGroup) {
        for (i in 0 until view.childCount) {
            collectViews(view.getChildAt(i), viewList)
        }
    }
}
相关推荐
海水冷却5 小时前
Android 群组聊天怎么实现:基于 ZIM 的完整落地指南(附可运行 Demo)
android
三少爷的鞋8 小时前
现代 Android 架构指南:不要再主动设计 Callback 接口
android
TimeFine14 小时前
AI 编码工作流的设计
android·ai编程
骑着蜗牛撵大象32714 小时前
C++20 协程 × 屏障:手写一个 co_await 版 Barrier,让等待不再钉死线程
android·c++20·协程·并发编程·co_await·屏障同步
山东孙燕姿16 小时前
设备台账重构:从一张表到三层模型
android·java·网络·低代码·重构·rxjava
蒸鱼Yuzheng17 小时前
移动端 CPU 调度分析:线程亲和性、大小核迁移与卡顿
android·perfetto·性能分析·线程优化·cpu调度
booboosui17 小时前
安卓唤醒词:12 维 MFCC 加 DTW,阈值 1.8
android
传奇开心果17 小时前
Compose Multiplatform 共享边界实战:架构契约、互操作同步与可回退设计
android·kotlin
事圆则缓17 小时前
从开机到首帧:Android 系统与应用启动流程源码分析
android
AI吃大瓜18 小时前
指针压力表读数4:Android实现指针压力表读数(可实时检测)
android·指针读数·指针压力表·压力表·压力表读数