当前位置:网站首页>Databinding+LiveData轻松实现无重启换肤
Databinding+LiveData轻松实现无重启换肤
2022-07-28 11:53:00 【塞尔维亚大叔】
作者:ezy
最近项目需要用到无重启动态换肤功能,本来打算用github上star最多的 Android-skin-support
但仔细一看发现太复杂而且2年没维护+大量issues没解决,最终放弃
经过探索,发现 Databinding+LiveData 能低成本实现无重启换肤
- 无重启动态换肤(不需要recreate())
- 无需制作皮肤包
- 无额外依赖(Databinding+LiveData本身几乎开发必备)
- 低侵入性
- AppCompat和Material组件默认支持(少量属性需要额外支持或适配)
- 自定义View/第三方View 适配过程简单(写个绑定适配器就行了)
- 不需要使用 LayoutInflater.Factory
定义皮肤
以下代码定义了三个皮肤Default,Day,Night,通过调用 AppTheme.update(theme) 就可以完成动态换肤
这里皮肤只支持ColorStateList,因为大部分场景只要 ColorStateList 就够了
如果想要,Drawable/String等各种资源都能支持
data class Theme(
val content: Int,
val background: Int,
)
object Themes {
val Default = Theme(Color.RED, Color.GRAY)
val Day = Theme(Color.BLACK, Color.WHITE)
val Night = Theme(Color.MAGENTA, Color.BLACK)
}
object AppTheme {
val background = MutableLiveData<ColorStateList>()
val content = MutableLiveData<ColorStateList>()
init {
update(Themes.Default)
}
fun update(theme: Theme) {
background.value = ColorStateList.valueOf(theme.background)
content.value = ColorStateList.valueOf(theme.content)
}
}
在布局文件中使用皮肤
直接引入AppTheme单例,livedata会关联生命周期,不用担心资源释放
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="ezy.demo.theme.AppTheme" />
</data>
<FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:background="#EEEEEE">
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" app:background="@{AppTheme.INSTANCE.background}" android:gravity="center" android:orientation="vertical">
<SeekBar android:layout_width="300dp" android:layout_height="wrap_content" android:max="100" android:progress="50" android:progressBackgroundTint="@{AppTheme.INSTANCE.background}" android:progressTint="@{AppTheme.INSTANCE.content}" android:thumb="@android:drawable/ic_btn_speak_now" android:thumbTint="@{AppTheme.INSTANCE.content}" />
<TextView android:layout_width="100dp" android:layout_height="100dp" android:layout_margin="10dp" android:gravity="center" android:text="Hello World!" android:textColor="@{AppTheme.INSTANCE.content}" app:drawableTint="@{AppTheme.INSTANCE.content}" app:drawableTopCompat="@android:drawable/ic_media_pause" />
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal">
<TextView android:id="@+id/btn_default" android:layout_width="100dp" android:layout_height="40dp" android:gravity="center" android:text="Default" android:textColor="@{AppTheme.INSTANCE.content}" />
<TextView android:id="@+id/btn_day" android:layout_width="100dp" android:layout_height="40dp" android:gravity="center" android:text="Day" android:textColor="@{AppTheme.INSTANCE.content}" />
<TextView android:id="@+id/btn_night" android:layout_width="100dp" android:layout_height="40dp" android:gravity="center" android:text="Night" android:textColor="@{AppTheme.INSTANCE.content}" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
</layout>
关联生命周期
实际上 Databinding+ObserverableField也能实现无重启换肤,但ObserverableField不能关联生命周期,资源释放麻烦一些
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
// 关联生命周期
binding.lifecycleOwner = this
binding.btnDefault.setOnClickListener {
AppTheme.update(Themes.Default) }
binding.btnDay.setOnClickListener {
AppTheme.update(Themes.Day) }
binding.btnNight.setOnClickListener {
AppTheme.update(Themes.Night) }
}
}
扩展支持的皮肤属性
DataBinding本身已经支持了大量属性,有部分未支持,需要自己实现
实际上就是写个绑定适配器,想要支持第三方控件或自定义控件都差不多,很简单
以下是一些例子
@SuppressLint("RestrictedApi")
@BindingMethods(
BindingMethod(type = ImageView::class, attribute = "tint", method = "setImageTintList")
)
object ThemeAdapter {
@BindingAdapter("background")
@JvmStatic
fun adaptBackground(view: View, value: ColorStateList?) {
view.setBackgroundColor(Color.WHITE)
view.backgroundTintList = value
}
@BindingAdapter("drawableTint")
@JvmStatic
fun adaptDrawableTint(view: TextView, value: ColorStateList?) {
if (view is AppCompatTextView) {
view.supportCompoundDrawablesTintList = value
}
}
@BindingAdapter("android:progressBackgroundTint")
@JvmStatic
fun adaptProgressBackgroundTint(view: SeekBar, value: ColorStateList?) {
view.progressBackgroundTintList = value
}
}
Demo
边栏推荐
- Connected Block & food chain - (summary of parallel search set)
- Science heavyweight: AI design protein has made another breakthrough, and it can design specific functional proteins
- 云原生—运行时环境
- IO流再回顾,深入理解序列化和反序列化
- Redefinition problem of defining int i variable in C for loop
- [embedded explanation] key scanning based on finite state machine and stm32
- 区块反转(暑假每日一题 7)
- 揭秘界面控件DevExpress WinForms为何弃用受关注的MaskBox属性
- Custom paging tag 02 of JSP custom tag
- 1331. Array sequence number conversion: simple simulation question
猜你喜欢
随机推荐
Leetcode206 reverse linked list
Leetcode 42. rainwater connection
MySQL总是安装不成功,这样处理就好啦
Linear classifier (ccf20200901)
Change the document type in endnode and import it in word
Which big model is better? Openbmb releases bmlist to give you the answer!
揭秘界面控件DevExpress WinForms为何弃用受关注的MaskBox属性
MSP430 开发中遇到的坑(待续)
leetcode 1518. 换酒问题
西门子对接Leuze BPS_304i 笔记
How to view win11 system and reserved space?
Ccf201912-2 recycling station site selection
LeetCode84 柱状图中最大的矩形
MySQL is always installed unsuccessfully. Just do it like this
Using JSON in C language projects
Flexpro software: measurement data analysis in production, research and development
leetcode 376. Wiggle Subsequence
Quick read in
Leetcode:704 binary search
Unity loads GLB model









