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)
        }
    }
}
相关推荐
Zhu7587 小时前
在k8s集群部署MySQL单实例,支持多个主流发行版
android·mysql·kubernetes
AFinalStone10 小时前
Android 7系统异常问题排查(四)Framework层(上)—System Server Watchdog
android·系统异常
宝杰X712 小时前
Android Room3 多平台数据库
android·数据库
huibin14785236914 小时前
热缓解学习记录
android·学习
小田的博客17 小时前
SAP MM 供应商银行主数据更新报错!message R1228!
android·java·服务器
一笑的小酒馆18 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android
只会cv的小前端20 小时前
七巧低代码服务端脚本使用方法
android·低代码·rxjava
20 小时前
Android 自定义 View 实战:从零打造工业级全向摇杆控件(OmniJoystickView)
android·kotlin·自定义view·摇杆控件
雨白20 小时前
深入理解 Kotlin 协程 (十一):以逸待劳,探秘 select 多路复用与并发安全策略
android·kotlin
hunterandroid1 天前
[Android 从零到一] Android 深度链接与 App Links:从 URI Scheme 到可验证的应用跳转
android