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)
        }
    }
}
相关推荐
JJay.1 小时前
Android BLE 断开重连为什么越来越不稳定
android
su_ym81102 小时前
Android.mk与Android.bp介绍
android
zhangphil2 小时前
Android Coil3图片解码Bitmap后存入磁盘,再次加载读磁盘Bitmap缓存
android·kotlin
我命由我123452 小时前
Android 开发问题:SharedPreferences 的 getString 方法返回值类型 Type mismatch 问题
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
gjc5922 小时前
直击MySQL致命坑!GROUP_CONCAT默认截断不报错
android·数据库·mysql
Min_小明3 小时前
Android ANR 排查指南(思路、方法与实战案例)
android
chenjixue3 小时前
记录下我理解的安卓,鸿蒙,ios, rn , fullter, Jetpack Compose,react 的相似与不同
android·华为·harmonyos
REDcker3 小时前
Android ADB 命令教程与速查
android·adb
书中有颜如玉3 小时前
Kotlin Coroutines 异步编程实战:从原理到生产级应用
android·开发语言·kotlin