原生实现
调用了切换方法以后,当前界面会重建,其他界面也一样,这样就会改变颜色。使用原生的方法实现比较简单,缺点就是切换效果不好(因为界面会重建,设置了动画效果也不好),界面会重建,而且就两种模式可选。
activity
/*** @Author: 13009* @CreateDate: 2021/8/3 13:57*@Email:kiwilss@163.com*@Description: 系统原生的方式实现夜间模式* 优点:方法简单,实现简单* 缺点:切换效果不好,只能实现两种模式,界面会重新绘制*/class SkinColorActivity: AppCompatActivity(R.layout.activity_skin_color) {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)Log.e(TAG, "onCreate: ")//获取当前模式btnSkinColorMode.setOnClickListener {val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASKLog.e(TAG, "onCreate: $mode")}btnSkinColorDay.setOnClickListener {val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASKif (mode == Configuration.UI_MODE_NIGHT_YES){//是夜间模式时修改// showAnimation()AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)window.setWindowAnimations(R.style.WindowAnimationFadeInOut)recreate()//设置后整个app都会生效,动画效果不好,使用recreate后界面数据丢失}}btnSkinColorNight.setOnClickListener {val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASKif (mode == Configuration.UI_MODE_NIGHT_NO){//是日间模式时修改// showAnimation()AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)/* window.setWindowAnimations(R.style.WindowAnimationFadeInOut)recreate()*/}}}override fun onResume() {super.onResume()Log.e(TAG, "onResume: ")}override fun onConfigurationChanged(newConfig: Configuration) {super.onConfigurationChanged(newConfig)Log.e(TAG, "onConfigurationChanged: ")}/*** 展示一个切换动画*/private fun showAnimation() {val decorView = window.decorViewval cacheBitmap: Bitmap? = getCacheBitmapFromView(decorView)if (decorView is ViewGroup && cacheBitmap != null) {val view = View(this)view.setBackgroundDrawable(BitmapDrawable(resources, cacheBitmap))val layoutParam = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)decorView.addView(view, layoutParam)val objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)objectAnimator.duration = 300objectAnimator.addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(animation: Animator) {super.onAnimationEnd(animation)decorView.removeView(view)}})objectAnimator.start()}}/*** 获取一个 View 的缓存视图** @param view* @return*/private fun getCacheBitmapFromView(view: View): Bitmap? {val drawingCacheEnabled = trueview.isDrawingCacheEnabled = drawingCacheEnabledview.buildDrawingCache(drawingCacheEnabled)val drawingCache = view.drawingCacheval bitmap: Bitmap?if (drawingCache != null) {bitmap = Bitmap.createBitmap(drawingCache)view.isDrawingCacheEnabled = false} else {bitmap = null}return bitmap}}
布局
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@color/skin_white"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试颜色变化相关"android:textColor="@color/skin_black"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_10"/><Buttonandroid:id="@+id/btnSkinColorMode"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="获取当前模式"android:textColor="@color/skin_red"android:background="@color/skin_yellow"android:layout_marginTop="@dimen/dp_10"android:layout_gravity="center"/><Buttonandroid:id="@+id/btnSkinColorNight"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="夜间模式"android:textColor="@color/skin_red"android:background="@color/skin_yellow"android:layout_marginTop="@dimen/dp_10"android:layout_gravity="center"/><Buttonandroid:id="@+id/btnSkinColorDay"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="日间模式"android:textColor="@color/skin_red"android:background="@color/skin_yellow"android:layout_marginTop="@dimen/dp_10"android:layout_gravity="center"/></LinearLayout>
颜色
values里颜色:<!-- 换肤相关颜色--><color name="skin_white">#FFFFFF</color><color name="skin_black">#000000</color><color name="skin_green">#00ff00</color><color name="skin_yellow">#ffff00</color><color name="skin_red">#ff0000</color>
values-night里的颜色:
<!-- 换肤相关颜色--><color name="skin_white">#000000</color><color name="skin_black">#FFFFFF</color><color name="skin_green">#00ff00</color><color name="skin_yellow">#ffff00</color><color name="skin_red">#ff0000</color>
记录模式,通过控件设置颜色实现
这种方式实现原理很简单,切换模式时,获取控件设置对应的背景或图片文字颜色,这种方式不用重启界面,切换效果就会好很多,缺点就是要对每个控件进行设置。
- activity
```
/**
- @Author: 13009
- @CreateDate: 2021/8/4 18:28 @Email:kiwilss@163.com @Description: 通过代码对控件修改颜色实现
- 优点:不用重启,切换效果很好
- 缺点:需要单独对每个控件设置颜色 */ class SkinColorActivity2 : AppCompatActivity(R.layout.activity_skin_color2) { private var width = 0 private var height = 0 private var statusBarHeight = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) llSkinRoot.post { getInitData() }
btnSkinToggle.setOnClickListener {//切换夜间toggleMode(true)}btnSkinToggle2.setOnClickListener {toggleMode(false)}}private fun toggleMode(isNight: Boolean) {/* if (isNight) {setNightTheme()} else {setDayTheme()}*/toggleDayNight(isNight)}private fun toggleDayNight(night: Boolean) {showAnimation()if (night){setNightThemeInfo()}else{setDayThemeInfo()}}/*** 展示一个切换动画*/private fun showAnimation() {val decorView = window.decorViewval cacheBitmap: Bitmap? = getCacheBitmapFromView(decorView)if (decorView is ViewGroup && cacheBitmap != null) {val view = View(this)view.setBackgroundDrawable(BitmapDrawable(resources, cacheBitmap))val layoutParam = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)decorView.addView(view, layoutParam)val objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)objectAnimator.duration = 500objectAnimator.addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(animation: Animator) {super.onAnimationEnd(animation)decorView.removeView(view)}})objectAnimator.start()}}/*** 获取一个 View 的缓存视图** @param view* @return*/private fun getCacheBitmapFromView(view: View): Bitmap? {val drawingCacheEnabled = trueview.isDrawingCacheEnabled = drawingCacheEnabledview.buildDrawingCache(drawingCacheEnabled)val drawingCache = view.drawingCacheval bitmap: Bitmap?if (drawingCache != null) {bitmap = Bitmap.createBitmap(drawingCache)view.isDrawingCacheEnabled = false} else {bitmap = null}return bitmap}/*** 设置夜间模式*/private fun setNightTheme() {val imageView = ImageView(this)imageView.layoutParams = ViewGroup.LayoutParams(width, height - statusBarHeight)val bitmap = loadBitmapFromView(llSkinRoot)imageView.setImageBitmap(bitmap)llSkinRoot.addView(imageView)//设置新主题setNightThemeInfo()val colorA = Color.parseColor("#ffffff")val colorB = Color.parseColor("#333444")val objectAnimator = ObjectAnimator.ofInt(imageView, "backgroundColor", colorA, colorB)objectAnimator.duration = 800objectAnimator.setEvaluator(ArgbEvaluator())objectAnimator.addListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator) {}override fun onAnimationEnd(animation: Animator) {llSkinRoot.removeView(imageView)}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {}})objectAnimator.start()}/*** 设置日间模式*/private fun setDayTheme() {val imageView = ImageView(this)imageView.layoutParams = ViewGroup.LayoutParams(width, height - statusBarHeight)imageView.scaleType = ImageView.ScaleType.CENTER_CROPval bitmap: Bitmap? = loadBitmapFromView(llSkinRoot)imageView.setImageBitmap(bitmap)llSkinRoot.addView(imageView)//设置新主题setDayThemeInfo()val colorA = Color.parseColor("#333444")val colorB = Color.parseColor("#ffffff")val objectAnimator = ObjectAnimator.ofInt(imageView, "backgroundColor", colorA, colorB)objectAnimator.duration = 800objectAnimator.setEvaluator(ArgbEvaluator())objectAnimator.addListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator) {}override fun onAnimationEnd(animation: Animator) {llSkinRoot.removeView(imageView)}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {}})objectAnimator.start()}/*** 设置夜间模式具体代码*/private fun setNightThemeInfo() {llSkinRoot.setBackgroundColor(Color.parseColor("#333444"))tvSkinText.setTextColor(Color.parseColor("#666666"))
// imageView.setImageResource(R.mipmap.night_icon) btnSkinToggle.setTextColor(ContextCompat.getColor(this,R.color.skin_black)) btnSkinToggle.setBackgroundColor(ContextCompat.getColor(this,R.color.skin_white)) btnSkinToggle2.setTextColor(ContextCompat.getColor(this,R.color.skin_black)) btnSkinToggle2.setBackgroundColor(ContextCompat.getColor(this,R.color.skin_white)) }
/*** 设置日渐模式具体代码*/private fun setDayThemeInfo() {llSkinRoot.setBackgroundColor(Color.parseColor("#FFFFFF"))tvSkinText.setTextColor(Color.parseColor("#222222"))
// imageView.setImageResource(R.mipmap.day_icom) btnSkinToggle.setTextColor(ContextCompat.getColor(this,R.color.skin_white)) btnSkinToggle.setBackgroundColor(ContextCompat.getColor(this,R.color.skin_black)) btnSkinToggle2.setTextColor(ContextCompat.getColor(this,R.color.skin_white)) btnSkinToggle2.setBackgroundColor(ContextCompat.getColor(this,R.color.skin_black)) }
/*** 获取view截图对应的bitmap* @param v* @return*/private fun loadBitmapFromView(v: View): Bitmap? {val b = Bitmap.createBitmap(width, height - statusBarHeight, Bitmap.Config.ARGB_8888)val c = Canvas(b)v.layout(0, 0, v.layoutParams.width, v.layoutParams.height)v.draw(c)return b}/*** 获取屏幕宽高和状态栏高度*/private fun getInitData() {val wm = this.windowManagerwidth = wm.defaultDisplay.widthheight = wm.defaultDisplay.height//获取status_bar_height资源的IDval resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")if (resourceId > 0) {//根据资源ID获取响应的尺寸值statusBarHeight = resources.getDimensionPixelSize(resourceId)}}
}
2. 界面
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><TextViewandroid:id="@+id/tvSkinText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="动画效果改变测试"android:textColor="@color/skin_black"android:layout_gravity="center"/><Buttonandroid:id="@+id/btnSkinToggle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="夜间模式"android:background="@color/skin_black"android:textColor="@color/skin_white"android:layout_gravity="center"/><Buttonandroid:id="@+id/btnSkinToggle2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="日间模式"android:background="@color/skin_black"android:textColor="@color/skin_white"android:layout_gravity="center"/></LinearLayout>
<a name="74a3bb78"></a>### 通过主题获取颜色,再用代码设置这种方法和上面实现类似,只是设置的颜色是由选中的主题获取,这样就可以设置很多个主题,实现多种颜色换肤。1. activity
/**
- @Author: 13009
- @CreateDate: 2021/8/4 19:39 @Email:kiwilss@163.com @Description: 通过修改主题,再修改代码颜色实现
- 优点:实现效果好,不需要重启,遍历当前界面控件更方便设置颜色,一种主题可以代表一种颜色
缺点:需要记录当前主题,每次进入设置对应的颜色 */ class SkinColorThemeActivity : AppCompatActivity(R.layout.activity_skin_color_theme) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)//这里可以获取保存的主题,再设置对应的颜色btnColorThemeDay.setOnClickListener {changeThemeTest(true)}btnColorThemeNight.setOnClickListener {changeThemeTest(false)}initRecycler()btnColorThemeJump.setOnClickListener {
// startActivityK
() startActivityForResult(Intent(this,SkinColorThemeActivity::class.java),1)}btnColorThemeJump2.setOnClickListener {setResult(RESULT_OK)finish()}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)Log.e(TAG, "onActivityResult: ")setTheme(R.style.Theme_MyApplication2)SkinHelp.instance.refreshUI(this)
}
private fun initRecycler() {val madapter = SkinColorThemeAdapter()val list = mutableListOf<String>("1", "", "", "")rvColorThemeList.run {layoutManager = LinearLayoutManager(this@SkinColorThemeActivity)adapter = madapter}madapter.setList(list)}private fun changeThemeTest(isDay: Boolean) {if (isDay) {setTheme(R.style.Theme_MyApplication)} else {setTheme(R.style.Theme_MyApplication2)}SkinHelp.instance.showAnimationAndRefreshUI(this)
// showAnimation() // refreshUI() //这里也可以修改状态栏颜色
}private fun refreshUI() {//获取主题对应的颜色val background = TypedValue() //背景色val textColor = TypedValue() //字体颜色val theme = themetheme.resolveAttribute(R.attr.main_bg, background, true)theme.resolveAttribute(R.attr.main_textcolor, textColor, true)//遍历控件设置颜色//注意这个vGroup并不是activity.xml中定义的根布局, mRootView才是。//注意这个vGroup并不是activity.xml中定义的根布局, mRootView才是。val vGroup = window.decorView.findViewById<View>(android.R.id.content) as ViewGroupval rootView = vGroup.getChildAt(0) as ViewGrouptraversalView(vGroup, background, textColor)}private fun traversalView(rootView: ViewGroup, background: TypedValue, textColor: TypedValue) {for (i in 0 until rootView.childCount) {val childVg = rootView.getChildAt(i)val tag = childVg.tag ?: ""Log.e(TAG, "traversalView: $tag")when (childVg) {is ViewGroup -> {if (TextUtils.equals(tag.toString(), "bg")) {childVg.setBackgroundColor(ContextCompat.getColor(this@SkinColorThemeActivity,background.resourceId))}traversalView(childVg, background, textColor)}is TextView -> {setTextViewSkin(tag, childVg, background, textColor)}is Button -> {setTextViewSkin(tag, childVg, background, textColor)}}}}private fun setTextViewSkin(tag: Any,childVg: TextView,background: TypedValue,textColor: TypedValue) {val tags = tag.toString().split(",")Log.e(TAG, "setTextViewSkin: $tags")when (tags.size) {1 -> {if (TextUtils.equals(tags[0], "textColor")) {childVg.setTextColor(ContextCompat.getColor(this@SkinColorThemeActivity,textColor.resourceId))} else if (TextUtils.equals(tags[0], "bg")) {childVg.setBackgroundColor(ContextCompat.getColor(this@SkinColorThemeActivity,background.resourceId))}}2 -> {if (TextUtils.equals(tags[0], "textColor")) {childVg.setTextColor(ContextCompat.getColor(this@SkinColorThemeActivity,textColor.resourceId))}if (TextUtils.equals(tags[1], "bg")) {childVg.setBackgroundColor(ContextCompat.getColor(this@SkinColorThemeActivity,background.resourceId))}}}}/***展示一个切换动画*/fun showAnimation2(activity: Activity) {val decorView = activity.window.decorView as ViewGroupval imageView = ImageView(activity)val width = activity.resources.displayMetrics.widthPixelsval height = activity.resources.displayMetrics.heightPixelsval bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)//生成截图,设置decorView.draw(Canvas(bitmap))imageView.background = BitmapDrawable(activity.resources, bitmap)imageView.layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)decorView.addView(imageView)//设置一个渐变动画val animator = ValueAnimator.ofFloat(1f,0f)animator.addUpdateListener {imageView.alpha = it.animatedValue as Float}animator.addListener {decorView.removeView(imageView)}animator.duration = 1000animator.start()}/*** 展示一个切换动画*/private fun showAnimation() {val decorView = window.decorViewval cacheBitmap: Bitmap? = getCacheBitmapFromView(decorView)if (decorView is ViewGroup && cacheBitmap != null) {val view = View(this)
// view.setBackgroundDrawable(BitmapDrawable(resources, cacheBitmap)) view.background = BitmapDrawable(resources, cacheBitmap) val layoutParam = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) decorView.addView(view, layoutParam) val objectAnimator = ObjectAnimator.ofFloat(view, “alpha”, 1f, 0f) objectAnimator.duration = 1000 objectAnimator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) decorView.removeView(view) } }) objectAnimator.start() } }
/*** 获取一个 View 的缓存视图** @param view* @return*/private fun getCacheBitmapFromView(view: View): Bitmap? {val drawingCacheEnabled = trueview.isDrawingCacheEnabled = drawingCacheEnabledview.buildDrawingCache(drawingCacheEnabled)val drawingCache = view.drawingCacheval bitmap: Bitmap?if (drawingCache != null) {bitmap = Bitmap.createBitmap(drawingCache)view.isDrawingCacheEnabled = false} else {bitmap = null}return bitmap}
}
2. 界面
<?xml version=”1.0” encoding=”utf-8”?>
<TextViewandroid:id="@+id/tvColorThemeText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="遍历所有控件,再通过切换主题实现"android:tag="textColor"android:textColor="?attr/main_textcolor"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_10"/><Buttonandroid:id="@+id/btnColorThemeNight"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="夜间"android:textColor="?attr/main_textcolor"android:background="?attr/main_bg"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_40"/><Buttonandroid:id="@+id/btnColorThemeDay"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="日间"android:textColor="?attr/main_textcolor"android:background="?attr/main_bg"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_40"/><Buttonandroid:id="@+id/btnColorThemeJump"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="跳转"android:textColor="?attr/main_textcolor"android:background="?attr/main_bg"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_40"/><Buttonandroid:id="@+id/btnColorThemeJump2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="切换"android:textColor="?attr/main_textcolor"android:background="?attr/main_bg"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_40"/>
<RelativeLayoutandroid:id="@+id/rlColorThemeTest"android:layout_width="match_parent"android:layout_height="100dp"android:background="?attr/main_bg"/><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/rvColorThemeList"android:layout_width="match_parent"android:layout_height="wrap_content"/>
3. adapter
class SkinColorThemeAdapter: BaseQuickAdapter
}
}
4. item
<?xml version=”1.0” encoding=”utf-8”?>
<TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试列表中是否有效"android:layout_centerInParent="true"android:tag="textColor"android:textColor="?attr/main_textcolor"/>
5. attrs
<?xml version=”1.0” encoding=”utf-8”?>
6. theme
<style name="TransparentTheme" parent="Theme.MyApplication"><item name="android:windowNoTitle">true</item><item name="android:windowIsTranslucent">true</item><item name="android:windowBackground">@color/translate</item></style><style name="WindowAnimationFadeInOut"><item name="android:windowEnterAnimation">@anim/fade_in</item><item name="android:windowExitAnimation">@anim/fade_out</item></style>
7. skinhelp
class SkinHelp private constructor() {
companion object {val instance = SkinHelpSingle.holder}private object SkinHelpSingle {val holder = SkinHelp()}/*** 展示一个切换动画,并刷新界面*/fun showAnimationAndRefreshUI(activity: Activity?) {if (activity == null) {return}val decorView = activity.window.decorViewval cacheBitmap: Bitmap? = getCacheBitmapFromView(decorView)if (decorView is ViewGroup && cacheBitmap != null) {val view = View(activity)
// view.setBackgroundDrawable(BitmapDrawable(resources, cacheBitmap)) view.background = BitmapDrawable(activity.resources, cacheBitmap) val layoutParam = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) decorView.addView(view, layoutParam) val objectAnimator = ObjectAnimator.ofFloat(view, “alpha”, 1f, 0f) objectAnimator.duration = 1000 objectAnimator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) decorView.removeView(view) } }) objectAnimator.start() } //刷新界面 refreshUI(activity) }
fun refreshUI(activity: Activity) {//获取主题对应的颜色并设置val background = TypedValue() //背景色val textColor = TypedValue() //字体颜色val theme = activity.themetheme.resolveAttribute(R.attr.main_bg, background, true)theme.resolveAttribute(R.attr.main_textcolor, textColor, true)val vGroup = activity.window.decorView.findViewById<View>(android.R.id.content) as ViewGroup
// val rootView = vGroup.getChildAt(0) as ViewGroup traversalView(vGroup, background, textColor) }
/*** 展示一个切换动画*/fun showAnimation(activity: Activity?) {if (activity == null) {return}val decorView = activity.window.decorViewval cacheBitmap: Bitmap? = getCacheBitmapFromView(decorView)if (decorView is ViewGroup && cacheBitmap != null) {val view = View(activity)
// view.setBackgroundDrawable(BitmapDrawable(resources, cacheBitmap)) view.background = BitmapDrawable(activity.resources, cacheBitmap) val layoutParam = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) decorView.addView(view, layoutParam) val objectAnimator = ObjectAnimator.ofFloat(view, “alpha”, 1f, 0f) objectAnimator.duration = 1000 objectAnimator.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) decorView.removeView(view) } }) objectAnimator.start() } } /* 遍历控件设置对应颜色
* @param vGroup* @param background* @param textColor*/private fun traversalView(vGroup: ViewGroup, background: TypedValue, textColor: TypedValue) {for (i in 0 until vGroup.childCount) {val childVg = vGroup.getChildAt(i)val tag = childVg.tagwhen (childVg) {is ViewGroup -> {if (tag != null &&TextUtils.equals("bg", tag.toString())) {childVg.setBackgroundColor(ContextCompat.getColor(MyApp.myApp,background.resourceId))}traversalView(childVg, background, textColor)}is TextView -> {tag ?: continuesetTextSkin(tag.toString(), childVg, background, textColor)}is Button -> {tag ?: continuesetTextSkin(tag.toString(), childVg, background, textColor)}}}}private fun setTextSkin(tagss: String,childVg: TextView,background: TypedValue,textColor: TypedValue) {val tags = tagss.split(",")if (tags.isNullOrEmpty()) returnif (tags.contains("bg")) {childVg.setBackgroundColor(ContextCompat.getColor(MyApp.myApp,background.resourceId))}if (tags.contains("textColor")) {childVg.setTextColor(ContextCompat.getColor(MyApp.myApp,textColor.resourceId))}}/*** 获取一个 View 的缓存视图** @param view* @return*/private fun getCacheBitmapFromView(view: View): Bitmap? {val drawingCacheEnabled = trueview.isDrawingCacheEnabled = drawingCacheEnabledview.buildDrawingCache(drawingCacheEnabled)val drawingCache = view.drawingCacheval bitmap: Bitmap?if (drawingCache != null) {bitmap = Bitmap.createBitmap(drawingCache)view.isDrawingCacheEnabled = false} else {bitmap = null}return bitmap}
}
<a name="f1fb8fd6"></a>### 主题 + 自定义View通过修改主题改变颜色,不过每个控件都要自定义,有点麻烦。1. activity
/**
- @Author: 13009
- @CreateDate: 2021/8/3 15:47 @Email:kiwilss@163.com @Description: 通过切换主题实现夜间模式,一套主题就是一种颜色,可以实现换肤
- 优点,不会重启当前activity,切换时效果非常好
缺点,需要在记录当前主题,在baseActivity里面设置,需要自定义所有需要用到的控件 */ class SkinThemeActivity: AppCompatActivity(R.layout.activity_skin_theme) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)//当前主题保存在数据库,可以随时获取btnSkinColorNight.setOnClickListener {/*setTheme(R.style.Theme_MyApplication2)change()*/
// showAnimation() // toggleThemeSetting()
setTheme(R.style.Theme_MyApplication2)change()}btnSkinColorDay.setOnClickListener {setTheme(R.style.Theme_MyApplication)change()}
}
fun change() {val rootView = window.decorViewrootView.isDrawingCacheEnabled = truerootView.buildDrawingCache(true)val localBitmap = Bitmap.createBitmap(rootView.drawingCache)rootView.isDrawingCacheEnabled = falseif (null != localBitmap && rootView is ViewGroup) {val tmpView = View(applicationContext)tmpView.setBackgroundDrawable(BitmapDrawable(resources, localBitmap))val params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)rootView.addView(tmpView, params)tmpView.animate().alpha(0f).setDuration(400).setListener(object : Animator.AnimatorListener {override fun onAnimationStart(animation: Animator) {ColorUiUtil.changeTheme(rootView, theme)System.gc()}override fun onAnimationEnd(animation: Animator) {rootView.removeView(tmpView)localBitmap.recycle()}override fun onAnimationCancel(animation: Animator) {}override fun onAnimationRepeat(animation: Animator) {}}).start()}//改变一下状态栏颜色//发送广播让所有界面全部改变}/*** 展示一个切换动画*/private fun showAnimation() {val decorView = window.decorViewval cacheBitmap: Bitmap? = getCacheBitmapFromView(decorView)if (decorView is ViewGroup && cacheBitmap != null) {val view = View(this)view.setBackgroundDrawable(BitmapDrawable(resources, cacheBitmap))val layoutParam = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT)decorView.addView(view, layoutParam)val objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f)objectAnimator.duration = 300objectAnimator.addListener(object : AnimatorListenerAdapter() {override fun onAnimationEnd(animation: Animator) {super.onAnimationEnd(animation)decorView.removeView(view)}})objectAnimator.start()}}/*** 获取一个 View 的缓存视图** @param view* @return*/private fun getCacheBitmapFromView(view: View): Bitmap? {val drawingCacheEnabled = trueview.isDrawingCacheEnabled = drawingCacheEnabledview.buildDrawingCache(drawingCacheEnabled)val drawingCache = view.drawingCacheval bitmap: Bitmap?if (drawingCache != null) {bitmap = Bitmap.createBitmap(drawingCache)view.isDrawingCacheEnabled = false} else {bitmap = null}return bitmap}
}
2. xml
<?xml version=”1.0” encoding=”utf-8”?>
<com.leapmotor.explame.ui.skin.theme.ColorTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="测试颜色变化相关"android:textColor="?attr/main_textcolor"android:layout_gravity="center"android:layout_marginTop="@dimen/dp_10"/><com.leapmotor.explame.ui.skin.theme.ColorButtonandroid:id="@+id/btnSkinColorMode"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="获取当前模式"android:textColor="?attr/main_textcolor"android:background="@color/skin_yellow"android:layout_marginTop="@dimen/dp_10"android:layout_gravity="center"/><com.leapmotor.explame.ui.skin.theme.ColorButtonandroid:id="@+id/btnSkinColorNight"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="夜间模式"android:textColor="?attr/main_textcolor"android:background="@color/skin_yellow"android:layout_marginTop="@dimen/dp_10"android:layout_gravity="center"/><com.leapmotor.explame.ui.skin.theme.ColorButtonandroid:id="@+id/btnSkinColorDay"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="日间模式"android:textColor="?attr/main_textcolor"android:background="@color/skin_yellow"android:layout_marginTop="@dimen/dp_10"android:layout_gravity="center"/>
3. ColorButton
@SuppressLint(“AppCompatCustomView”) public class ColorButton extends Button implements ColorUiInterface {
private int attr_textColor = -1;private int attr_background = -1;public ColorButton(Context context) {this(context,null);}public ColorButton(Context context, AttributeSet attrs) {this(context, attrs,0);}public ColorButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);this.attr_background = ViewAttributeUtil.getBackgroundAttibute(attrs);}@Overridepublic View getView() {return this;}@Overridepublic void setTheme(Resources.Theme themeId) {if (attr_textColor != -1) {ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor);}if (attr_background != -1) {ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_background);}}
}
4. COlorLinearLayout
public class ColorLinerLayout extends LinearLayout implements ColorUiInterface{
private int attr_backgound = -1;public ColorLinerLayout(Context context) {super(context);}public ColorLinerLayout(Context context, AttributeSet attrs) {super(context, attrs);this.attr_backgound = ViewAttributeUtil.getBackgroundAttibute(attrs);}public ColorLinerLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);this.attr_backgound = ViewAttributeUtil.getBackgroundAttibute(attrs);}@Overridepublic View getView() {return this;}@Overridepublic void setTheme(Resources.Theme themeId) {if(attr_backgound != -1) {ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_backgound);}}
}
5. ColorTextView
@SuppressLint(“AppCompatCustomView”) public class ColorTextView extends TextView implements ColorUiInterface {
private int attr_drawable = -1;private int attr_textAppearance = -1;private int attr_textColor = -1;private int attr_textLinkColor = -1;private int attr_background = -1;public ColorTextView(Context context) {this(context,null);}public ColorTextView(Context context, AttributeSet attrs) {this(context, attrs,0);
// this.attr_textAppearance = ViewAttributeUtil.getTextApperanceAttribute(attrs); / this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs); this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs); this.attr_textLinkColor = ViewAttributeUtil.getTextLinkColorAttribute(attrs);/ }
public ColorTextView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);
// this.attr_textAppearance = ViewAttributeUtil.getTextApperanceAttribute(attrs); this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs); this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs); this.attr_textLinkColor = ViewAttributeUtil.getTextLinkColorAttribute(attrs); this.attr_background = ViewAttributeUtil.getBackgroundAttibute(attrs); }
@Overridepublic View getView() {return this;}@Overridepublic void setTheme(Resources.Theme themeId) {Log.d("COLOR", "id = " + getId());if (attr_drawable != -1) {ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_drawable);}
// if(attr_textAppearance != -1) { // ViewAttributeUtil.applyTextAppearance(this, themeId, attr_textAppearance); // } if (attr_textColor != -1) { ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor); } if (attr_textLinkColor != -1) { ViewAttributeUtil.applyTextLinkColor(this, themeId, attr_textLinkColor); } if (attr_background != -1) { ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_background); } } }
6. ColorUiInterface
public interface ColorUiInterface {
View getView();void setTheme(Resources.Theme themeId);
}
7. ColorUiUtil
public class ColorUiUtil { /**
* 切换应用主题** @param rootView*/public static void changeTheme(View rootView, Resources.Theme theme) {if (rootView instanceof ColorUiInterface) {((ColorUiInterface) rootView).setTheme(theme);if (rootView instanceof ViewGroup) {int count = ((ViewGroup) rootView).getChildCount();for (int i = 0; i < count; i++) {changeTheme(((ViewGroup) rootView).getChildAt(i), theme);}}if (rootView instanceof AbsListView) {try {Field localField = AbsListView.class.getDeclaredField("mRecycler");localField.setAccessible(true);@SuppressLint({"DiscouragedPrivateApi", "PrivateApi"})Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear");localMethod.setAccessible(true);localMethod.invoke(localField.get(rootView));} catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) {e1.printStackTrace();}}} else {if (rootView instanceof ViewGroup) {int count = ((ViewGroup) rootView).getChildCount();for (int i = 0; i < count; i++) {changeTheme(((ViewGroup) rootView).getChildAt(i), theme);}}if (rootView instanceof AbsListView) {try {Field localField = AbsListView.class.getDeclaredField("mRecycler");localField.setAccessible(true);@SuppressLint({"DiscouragedPrivateApi", "PrivateApi"})Method localMethod = Class.forName("android.widget.AbsListView$RecycleBin").getDeclaredMethod("clear");localMethod.setAccessible(true);localMethod.invoke(localField.get(rootView));} catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e1) {e1.printStackTrace();}}}}
}
9. ViewAttributeUtil
public class ViewAttributeUtil {
public static int getAttributeValue(AttributeSet attr, int paramInt) {int value = -1;int count = attr.getAttributeCount();for (int i = 0; i < count; i++) {if (attr.getAttributeNameResource(i) == paramInt) {String str = attr.getAttributeValue(i);if (null != str && str.startsWith("?")) {value = Integer.valueOf(str.substring(1, str.length())).intValue();return value;}}}return value;}public static int getBackgroundAttibute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.background);}public static int getCheckMarkAttribute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.checkMark);}public static int getSrcAttribute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.src);}public static int getTextApperanceAttribute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.textAppearance);}public static int getDividerAttribute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.divider);}public static int getTextColorAttribute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.textColor);}public static int getTextLinkColorAttribute(AttributeSet attr) {return getAttributeValue(attr, android.R.attr.textColorLink);}public static void applyBackgroundDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});Drawable drawable = ta.getDrawable(0);if (null != ci) {(ci.getView()).setBackgroundDrawable(drawable);}ta.recycle();}public static void applyImageDrawable(ColorUiInterface ci, Resources.Theme theme, int paramInt) {TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});Drawable drawable = ta.getDrawable(0);if (null != ci && ci instanceof ImageView) {((ImageView) ci.getView()).setImageDrawable(drawable);}ta.recycle();}public static void applyTextAppearance(ColorUiInterface ci, Resources.Theme theme, int paramInt) {TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});int resourceId = ta.getResourceId(0, 0);if (null != ci && ci instanceof TextView) {((TextView) ci.getView()).setTextAppearance(ci.getView().getContext(), resourceId);}ta.recycle();}public static void applyTextColor(ColorUiInterface ci, Resources.Theme theme, int paramInt) {TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});int resourceId = ta.getColor(0, 0);if (null != ci && ci instanceof TextView) {((TextView) ci.getView()).setTextColor(resourceId);}ta.recycle();}public static void applyTextLinkColor(ColorUiInterface ci, Resources.Theme theme, int paramInt) {TypedArray ta = theme.obtainStyledAttributes(new int[]{paramInt});int resourceId = ta.getColor(0, 0);if (null != ci && ci instanceof TextView) {((TextView) ci.getView()).setLinkTextColor(resourceId);}ta.recycle();}
}
<a name="bb8d6a84"></a>### skin库github:[https://github.com/hongyangAndroid/AndroidChangeSkin](https://github.com/hongyangAndroid/AndroidChangeSkin)activity:
class SkinMActivity: AppCompatActivity(R.layout.activity_skinm) { var mIsDay = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) SkinManager.getInstance().register(this)
//daybtnSkinMToggle.setOnClickListener {SkinHelp.instance.showAnimation(this)SkinManager.getInstance().changeSkin("black")}//nightbtnSkinMToggle2.setOnClickListener {SkinHelp.instance.showAnimation(this)SkinManager.getInstance().changeSkin("white")}}override fun onDestroy() {super.onDestroy()SkinManager.getInstance().unregister(this)}
}
<?xml version=”1.0” encoding=”utf-8”?>
<TextViewandroid:layout_width="match_parent"android:layout_height="50dp"android:background="@color/text_color"android:tag="skin:text_color:background" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:tag="skin:text_color:textColor"android:text="测试skin库应用内换肤"android:textColor="@color/text_color" /><Buttonandroid:id="@+id/btnSkinMToggle"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="日间"android:textColor="@color/text_color_black" /><Buttonandroid:id="@+id/btnSkinMToggle2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:text="夜间"android:textColor="@color/text_color_black" />
<color name="text_color">#FFff0000</color><color name="text_color_black">#FF000000</color><color name="text_color_white">#FFFFFFFF</color><color name="bg">#FFBB86FC</color><color name="bg_white">#000000</color><color name="bg_black">#FFFFFF</color>
参考
- 博客
探究APP换肤机制的设计与实现
日夜间及换肤(二)-原理分析
Android换肤方案总结
Android 主题换肤技术方案分析
- 第三方库
