原生实现
调用了切换方法以后,当前界面会重建,其他界面也一样,这样就会改变颜色。使用原生的方法实现比较简单,缺点就是切换效果不好(因为界面会重建,设置了动画效果也不好),界面会重建,而且就两种模式可选。
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_MASK
Log.e(TAG, "onCreate: $mode")
}
btnSkinColorDay.setOnClickListener {
val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
if (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_MASK
if (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.decorView
val 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 = 300
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 = true
view.isDrawingCacheEnabled = drawingCacheEnabled
view.buildDrawingCache(drawingCacheEnabled)
val drawingCache = view.drawingCache
val 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">
<TextView
android: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"/>
<Button
android: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"
/>
<Button
android: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"
/>
<Button
android: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.decorView
val 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 = 500
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 = true
view.isDrawingCacheEnabled = drawingCacheEnabled
view.buildDrawingCache(drawingCacheEnabled)
val drawingCache = view.drawingCache
val 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 = 800
objectAnimator.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_CROP
val 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 = 800
objectAnimator.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.windowManager
width = wm.defaultDisplay.width
height = wm.defaultDisplay.height
//获取status_bar_height资源的ID
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
//根据资源ID获取响应的尺寸值
statusBarHeight = resources.getDimensionPixelSize(resourceId)
}
}
}
2. 界面
<?xml version=”1.0” encoding=”utf-8”?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvSkinText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="动画效果改变测试"
android:textColor="@color/skin_black"
android:layout_gravity="center"/>
<Button
android: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"/>
<Button
android: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 = theme
theme.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 ViewGroup
val rootView = vGroup.getChildAt(0) as ViewGroup
traversalView(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 ViewGroup
val imageView = ImageView(activity)
val width = activity.resources.displayMetrics.widthPixels
val height = activity.resources.displayMetrics.heightPixels
val 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 = 1000
animator.start()
}
/**
* 展示一个切换动画
*/
private fun showAnimation() {
val decorView = window.decorView
val 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 = true
view.isDrawingCacheEnabled = drawingCacheEnabled
view.buildDrawingCache(drawingCacheEnabled)
val drawingCache = view.drawingCache
val 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”?>
<TextView
android: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"/>
<Button
android: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"/>
<Button
android: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"/>
<Button
android: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"/>
<Button
android: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"/>
<RelativeLayout
android:id="@+id/rlColorThemeTest"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="?attr/main_bg"/>
<androidx.recyclerview.widget.RecyclerView
android: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”?>
<TextView
android: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.decorView
val 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.theme
theme.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.decorView
val 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.tag
when (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 ?: continue
setTextSkin(tag.toString(), childVg, background, textColor)
}
is Button -> {
tag ?: continue
setTextSkin(tag.toString(), childVg, background, textColor)
}
}
}
}
private fun setTextSkin(
tagss: String,
childVg: TextView,
background: TypedValue,
textColor: TypedValue
) {
val tags = tagss.split(",")
if (tags.isNullOrEmpty()) return
if (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 = true
view.isDrawingCacheEnabled = drawingCacheEnabled
view.buildDrawingCache(drawingCacheEnabled)
val drawingCache = view.drawingCache
val 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.decorView
rootView.isDrawingCacheEnabled = true
rootView.buildDrawingCache(true)
val localBitmap = Bitmap.createBitmap(rootView.drawingCache)
rootView.isDrawingCacheEnabled = false
if (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.decorView
val 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 = 300
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 = true
view.isDrawingCacheEnabled = drawingCacheEnabled
view.buildDrawingCache(drawingCacheEnabled)
val drawingCache = view.drawingCache
val 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.ColorTextView
android: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.ColorButton
android: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.ColorButton
android: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.ColorButton
android: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);
}
@Override
public View getView() {
return this;
}
@Override
public 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);
}
@Override
public View getView() {
return this;
}
@Override
public 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); }
@Override
public View getView() {
return this;
}
@Override
public 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)
//day
btnSkinMToggle.setOnClickListener {
SkinHelp.instance.showAnimation(this)
SkinManager.getInstance().changeSkin("black")
}
//night
btnSkinMToggle2.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”?>
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@color/text_color"
android:tag="skin:text_color:background" />
<TextView
android: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" />
<Button
android: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" />
<Button
android: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 主题换肤技术方案分析
- 第三方库