[TOC]
原地址:# 日夜间及换肤(一)-常用技巧
总览
实现日夜间的方式有多种,基本可以整理如下:
- 设置UiMode来设置
- 对Activity设置主题来变换
- 动态设置资源,控制view刷新
UIMode实现
-
在value的同级目录下新建一个
values-night
的目录,其中新建colors.xml文件,themes.xml文件等需要日夜间切换 -
比如colors文件,需要将color所对应的名字保持一致,只进行颜色上的更改,达到日夜间不同的效果
//normal <color name="background">#FFFFFFFF</color> //白色 <color name="text_color">#FF000000</color> //黑色 //night <color name="background">#FF000000</color> //黑色 <color name="text_color">#FFFFFFFF</color> //白色
-
在需要触发切换的地方调用如下代码
//设置所有的Activity生效 if (AppCompatDelegate.getDefaultNightMode() == AppCompatDelegate.MODE_NIGHT_YES) { //切换白天 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) } else { //切换夜间 AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) } //设置所在的Activity生效 if(delegate.localNightMode == AppCompatDelegate.MODE_NIGHT_YES){ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { //切换白天 delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_NO } }else{ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { //切换夜间 delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES } } //五种参数类型 //跟随系统 MODE_NIGHT_FOLLOW_SYSTEM //跟随时间自动设置 MODE_NIGHT_AUTO_TIME //日间 MODE_NIGHT_NO //夜间 MODE_NIGHT_YES //根据系统电量决定暗色和亮色展示 MODE_NIGHT_AUTO_BATTERY
系统会根据日夜间自动去取*-night
或者正常的文件夹相对应的配置,配合持久化存储进行日夜间状态存储,二次打开加载上一次设置的模式
设置Activity主题实现
-
在colors.xml中新建日夜间两种颜色
<?xml version="1.0" encoding="utf-8"?> <resources> <color name="background_day">#FFFFFFFF</color> <color name="background_night">#FF000000</color> </resources>
-
在res/values目录下新建attrs.xml文件,添加自定义属性
<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="custom_bg" format="color|reference"/> </resources>
-
在res/values目录下新建themes.xml文件,添加两种主题如下:
<resources xmlns:tools="http://schemas.android.com/tools"> <style name="Theme.MyDay" parent="Theme.AppCompat"> <item name="custom_bg">@color/background_day</item> </style> <style name="Theme.MyNight" parent="Theme.AppCompat"> <item name="custom_bg">@color/background_night</item> </style> </resources>
-
在布局文件中使用自定义属性颜色
<androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/custom_bg" tools:context=".MainActivity"> ... </androidx.core.widget.NestedScrollView>
-
在Activity中配置
-
只在当前Activity中生效
class MainActivity : AppCompatActivity() { private var myTheme = R.style.Theme_MyDay private lateinit var binding:ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 判断是否有主题存储 if(savedInstanceState != null){ myTheme = savedInstanceState.getInt("theme") setTheme(myTheme) } binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) initView() } private fun initView() { binding.button2.setOnClickListener { myTheme = if(myTheme == R.style.Theme_MyDay){ R.style.Theme_MyNight }else{ R.style.Theme_MyDay } recreate() } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putInt("theme", myTheme) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) myTheme = savedInstanceState.getInt("theme") } }
因为setContentView()时进行主题加载和布局渲染,所以需要在setContentView()之前调用
setTheme
方法,如需动态替换日夜间模式,同样需要recreate()
才能生效,因为牵扯到Activity的重建,所以我们第一反应是通过onSaveInstance存储主题,在onCreate中进行加载,当然也可以使用其他方式。 -
在所有Activity生效
class App : Application() { companion object{ var myTheme = R.style.Theme_MyDay } var activityLifecycle = object : ActivityLifecycleCallbacks { override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { activity.setTheme(myTheme) } override fun onActivityStarted(activity: Activity) { } override fun onActivityResumed(activity: Activity) { } override fun onActivityPaused(activity: Activity) { } override fun onActivityStopped(activity: Activity) { } override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { } override fun onActivityDestroyed(activity: Activity) { } } override fun onCreate() { super.onCreate() registerActivityLifecycleCallbacks(activityLifecycle) } }
在Application中进行设置,用静态对象存储主题,监听应用下所有Activity的创建,在
onActivityCreate()
时,进行主题设置,这样对Activity来说是无感的,只需要设置主题即可。当然也可以做一个持久化存储,这里只是提供一个思路
-
动态设置资源,控制view刷新
比较代表性的就是Android-skin-support这个库
添加如下依赖:
implementation 'skin.support:skin-support:4.0.5' // skin-support
implementation 'skin.support:skin-support-appcompat:4.0.5' // skin-support 基础控件支持
implementation 'skin.support:skin-support-design:4.0.5' // skin-support-design material design 控件支持[可选]
implementation 'skin.support:skin-support-cardview:4.0.5' // skin-support-cardview CardView 控件支持[可选]
implementation 'skin.support:skin-support-constraint-layout:4.0.5' // skin-support-constraint-layout ConstraintLayout 控件支持[可选]
在Application的onCreate中进行初始化操作
@Override
public void onCreate() {
super.onCreate();
SkinCompatManager.withoutActivity(this)
.addInflater(new SkinAppCompatViewInflater()) // 基础控件换肤初始化
.addInflater(new SkinMaterialViewInflater()) // material design 控件换肤初始化[可选]
.addInflater(new SkinConstraintViewInflater()) // ConstraintLayout 控件换肤初始化[可选]
.addInflater(new SkinCardViewInflater()) // CardView v7 控件换肤初始化[可选]
.setSkinStatusBarColorEnable(false) // 关闭状态栏换肤,默认打开[可选]
.setSkinWindowBackgroundEnable(false) // 关闭windowBackground换肤,默认打开[可选]
.loadSkin();
}
如果项目中使用的Activity继承自AppCompatActivity,需要重载getDelegate()方法
@NonNull
@Override
public AppCompatDelegate getDelegate() {
return SkinAppCompatDelegateImpl.get(this, this);
}
通过如下方法触发
SkinCompatManager.getInstance().loadSkin("night", SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN); // 后缀加载
详细用法查看README
对比
- UIMode和theme替换都需要重建Activity,所以会导致闪屏,动态替换会更加流畅
- recreate()是在API 11添加进来的,所以会在Android 2.x中使用抛出异常
- theme换肤起来会更加方便