Android 夜间模式主题切换
Android开发中经常会遇到夜间模式等主题切换的需求,最常见的方案是通过setTheme切换Activity主题来实现。但是这种方案的缺点是每次切换主题后需要restart整个Activity才能生效。这里对此方案进行了改造,可以不重启Activity而立即生效:
1. 自定义属性
attrs.xml中,为需要随theme变化的内容定义属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MultipleTheme">
<attr name="color3" format="reference"/>
<attr name="color5" format="reference"/>
<attr name="color6" format="reference"/>
<attr name="color9" format="reference"/>
</declare-styleable>
</resources>
<attr/>
里可以定义各种属性类型,如color、float、integer、boolean、dimension(sp、dp/dip、px、pt…)、reference(指向本地资源)等。
2. 资源值
上面例子中的类型为资源的reference
,我们需要定义一些资源值,后面需要设置到自定义属性上:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="color3">#666666</color>
<color name="color3_night">#999999</color>
<color name="color5">#BBBBBB</color>
<color name="color5_night">#333333</color>
<color name="color6">#EEEEEE</color>
<color name="color6_night">#474747</color>
<color name="color9">@android:color/white</color>
<color name="color9_night">#232323</color>
</resources>
3. 主题theme
在不同的theme中,对属性设置不同的值,在styles.xml中定义theme如下
<resources>
<style name="DayModeTheme">
<item name="color3">@color/color3</item>
<item name="color5">@color/color5</item>
<item name="color6">@color/color6</item>
<item name="color9">@color/color9</item>
</style>
<style name="NightModeTheme">
<item name="color3">@color/color3_night</item>
<item name="color5">@color/color5_night</item>
<item name="color6">@color/color6_night</item>
<item name="color9">@color/color9_night</item>
</style>
</resources>
不同的Theme下同一个attr
的值不同,例如color3
在DayMode下是#666666
,而在NightMode下是#999999
4. 自定义控件
定义一系列的自定义控件,让其支持动态切换主题,例如ThemedTextView,通过setTheme方法可以动态的切换背景色和字体颜色
public class ThemedTextView extends TextView implements ThemeUiInterface {
private int attr_textAppearance = -1;
private int attr_textColor = -1;
public ThemedTextView(Context context) {
super(context);
}
public ThemedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
}
public ThemedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.attr_drawable = ViewAttributeUtil.getBackgroundAttibute(attrs);
this.attr_textColor = ViewAttributeUtil.getTextColorAttribute(attrs);
}
@Override
public View getView() {
return this;
}
@Override
public void setTheme(Resources.Theme themeId) {
if (attr_drawable != -1) {
ViewAttributeUtil.applyBackgroundDrawable(this, themeId, attr_drawable);
}
if (attr_textColor != -1) {
ViewAttributeUtil.applyTextColor(this, themeId, attr_textColor);
}
}
}
所以的自定义View继承自ThemeUiInterface
接口,
public interface ThemeUiInterface {
View getView();
void setTheme(Resources.Theme themeId);
}
当切换主题时,我们会从DecorView开始便利整个视图树,当遇到实现了此接口的View时,就调用setTheme方法使其更换资源
5. layout文件
layout中使用上述自定义View构架布局,并通过?attr/属性名
,来获取不同theme对应的值
<?xml version="1.0" encoding="utf-8"?>
<ThemedFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ThemedTextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:textColor="?attr/color3" />
</ThemedFrameLayout>
6. 注册Activity
Activity的onCreate的时候将自己注册到ThemeManager中
public class MultiThemedActivity extends Activity {
@Override
protected void onDestroy() {
super.onDestroy();
ThemeManager.unregisterMultipleTheme(this);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeManager.registerMultipleTheme(this);
}
}
当调用ThemeManager的changeTheme方法时,遍历所有已注册的Activity,对其应用主题(从DecorView开始更细所有子View的资源)
public static void changeTheme(String group, String key) {
for (Activity activity : sActivityList) {
changeTheme(activity, group, key);
}
}
最终效果如下:
代码地址
上一篇: 浅谈PHP变量作用域以及地址引用问题
下一篇: Android 简单的白天与夜晚模式切换