在工作中实现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)
}
}
}