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)
        }
    }
}
相关推荐
俩个逗号。。10 小时前
Gradle 踩过的坑
android
土星碎冰机13 小时前
ai自学笔记(3.安卓篇,制作app
android·笔记·ai
随遇丿而安14 小时前
专题:Glide / Coil / Fresco,不是三种写法,而是三套图片加载思路
android
只可远观15 小时前
Android 自动埋点(页面打开 / 关闭 + 点击事件)完整方案
android·kotlin
私人珍藏库15 小时前
【Android】小小最新AI--千变万化扮演任何角色--沉浸式互动
android·app·工具·软件·多功能
zh_xuan16 小时前
Android MVI架构
android·mvi
测试开发-学习笔记17 小时前
Airtest+Poco快速上手
android·其他
李斯维17 小时前
Android Jetpack 简介:由来和演进
android·android studio·android jetpack
阿巴斯甜17 小时前
ARouter 的使用:
android
沐言人生17 小时前
ReactNative 源码分析9——Native View初始化
android·react native