项目有一个功能是用户在信息页面更改了性别,返回页面要更换主题。
方案一:
1.onRestart的时候切换主题,调用recreate()
2.onCreate或者setContentView的时候(只在当前Activity里设置一次)设置主题
ChildActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { this.setTheme(); super.onCreate(savedInstanceState);setContentView(R.layout.layout); }
protected void setTheme() { if (!this.mIsThemeSet) { this.mIsThemeSet = true; this.setTheme(getCurrentTheme()); } }
public static int getCurrentTheme() { return getGenderEnum() == GenderEnum.MALE ? R.style.lechild_theme_boy : R.style.lechild_theme_girl; }
theme_styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="lechild_theme_girl" parent="@style/LeChildAppTheme"> <item name="lechild_drawable_lechild_cal_bg">@drawable/lechild_cal_bg</item>...
</style>
<style name="lechild_theme_boy" parent="@style/LeChildAppTheme"> <item name="lechild_drawable_lechild_cal_bg">@drawable/lechild_cal_bg_boy</item>...
</style>
</resources>
theme_attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources><attr name="lechild_drawable_lechild_cal_bg" format="reference"/>...</resources>
layout.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/lechild_drawable_lechild_cal_bg">
3.缺陷:这样因为重新调用activity的生命周期,会造成页面闪一下,显示效果不好。
改进方案二:
gitHub上有一些开源项目,解决了闪的问题,找了个比较典型的叫ChangeThemeForAndoird,
1.核心思想就是针对所有的布局view及其子view,利用build构建Setter,根据代码动态的设置view的backgroundDrawable、textColor等属性。
MainActivity.java
@Override protected void onRestart() { super.onRestart(); changeThemeWithColorful(); }
/** * 设置各个视图与颜色属性的关联 */ private void setupColorful() { ViewGroupSetter listViewSetter = new ViewGroupSetter(mNewsListView); // 绑定ListView的Item View中的news_title视图,在换肤时修改它的text_color属性 listViewSetter.childViewTextColor(R.id.news_title, R.attr.text_color); listViewSetter.childViewBgDrawable(R.id.news_creator, R.attr.ic_launcher); // 构建Colorful对象来绑定View与属性的对象关系 mColorful = new Colorful.Builder(this) .backgroundDrawable(R.id.root_view, R.attr.root_view_bg) // 设置view的背景图片 .backgroundColor(R.id.change_btn, R.attr.btn_bg) // 设置背景色 .textColor(R.id.textview, R.attr.text_color) .setter(listViewSetter) // 手动设置setter .create(); // 设置文本颜色 } /** * 切换主题 */ private void changeThemeWithColorful() { if (!isNight) { mColorful.setTheme(R.style.NightTheme); } else { mColorful.setTheme(R.style.DayTheme); } isNight = !isNight; }
ColorFul.java
/** * 设置新的主题 * * @param newTheme */ public void setTheme(int newTheme) { mBuilder.setTheme(newTheme); } /** * * 构建Colorful的Builder对象 * * @author mrsimple * */ public static class Builder { /** * 存储了视图和属性资源id的关系表 */ Set<ViewSetter> mElements = new HashSet<ViewSetter>(); /** * 目标Activity */ Activity mActivity; /** * @param activity */ public Builder(Activity activity) { mActivity = activity; } /** * * @param fragment */ public Builder(Fragment fragment) { mActivity = fragment.getActivity(); } private View findViewById(int viewId) { return mActivity.findViewById(viewId); } /** * 将View id与存储该view背景色的属性进行绑定 * * @param viewId * 控件id * @param colorId * 颜色属性id * @return */ public Builder backgroundColor(int viewId, int colorId) { mElements.add(new ViewBackgroundColorSetter(findViewById(viewId), colorId)); return this; } /** * 将View id与存储该view背景Drawable的属性进行绑定 * * @param viewId * 控件id * @param colorId * Drawable属性id * @return */ public Builder backgroundDrawable(int viewId, int drawableId) { mElements.add(new ViewBackgroundDrawableSetter( findViewById(viewId), drawableId)); return this; } /** * 将TextView id与存储该TextView文本颜色的属性进行绑定 * * @param viewId * TextView或者TextView子类控件的id * @param colorId * 颜色属性id * @return */ public Builder textColor(int viewId, int colorId) { TextView textView = (TextView) findViewById(viewId); mElements.add(new TextColorSetter(textView, colorId)); return this; } /** * 用户手动构造并且添加Setter * * @param setter * 用户自定义的Setter * @return */ public Builder setter(ViewSetter setter) { mElements.add(setter); return this; } /** * 设置新的主题 * * @param newTheme */ protected void setTheme(int newTheme) { mActivity.setTheme(newTheme); makeChange(newTheme); } /** * 修改各个视图绑定的属性 */ private void makeChange(int themeId) { Theme curTheme = mActivity.getTheme(); for (ViewSetter setter : mElements) { setter.setValue(curTheme, themeId); } } /** * 创建Colorful对象 * * @return */ public Colorful create() { return new Colorful(this); }
各种setter.java
** * View的背景Drawabler Setter * @author mrsimple * */ public final class ViewBackgroundDrawableSetter extends ViewSetter { public ViewBackgroundDrawableSetter(View targetView, int resId) { super(targetView, resId); } public ViewBackgroundDrawableSetter(int viewId, int resId) { super(viewId, resId); } @SuppressWarnings("deprecation") @Override public void setValue(Theme newTheme, int themeId) { if ( mView == null ) { return ; } TypedArray a = newTheme.obtainStyledAttributes(themeId, new int[] { mAttrResId }); int attributeResourceId = a.getResourceId(0, 0); Drawable drawable = mView.getResources().getDrawable( attributeResourceId); a.recycle(); mView.setBackgroundDrawable(drawable); } }
2.缺陷:需要把所有的view都用代码替换,繁琐。
方案三:
github上开源项目Android-Skin-Loader,可以直接拿来做换肤用。这个库的核心思想就是动态的去加载第三方包里面的包,获取到其Resources然后以获取到的这个Resources去获取第三方包里面的资源内容,最后设置到我们有需响应皮肤更改的View上。
覆盖application的getResource方法,实现自己的resource,优先加载本地皮肤包文件夹下的资源包.
尽管动态加载方案比较黑科技,可能因为系统API的更改而出问题,但相对来所
好处有
- 灵活性高,后台可以随时更新皮肤包
- 相对透明,开发者几乎不用关心有几套皮肤,不用去定义各种theme和attr,甚至连皮肤包的打包都可以交给设计或者专门的同学
- apk体积节省
存在的问题
没有完善的开源项目,如果我们采用动态加载的第二种方案,需要的项目功能包括: - 自定义皮肤包结构
- 换肤引擎,加载皮肤包资源并load,实时刷新。
- 皮肤包打包工具
- 对各种rom的兼容
这些是从别人的博客里看的(http://blog.zhaiyifan.cn/2015/09/10/Android换肤技术总结/),自己没有详细看,代码还是比较复杂的。
项目最终使用的方案:
基于简单的原则,最终解决方案是,在更改性别的页面,按返回键时,加上一个loading圈,在服务端请求返回后,通知需要更改皮肤的页面,并延迟500ms销毁更改性别页面,此时更改皮肤的页面的皮肤已重新recreate()并完成主题切换,因此已经是切换后的皮肤。
怎么样,还是比较机智的girl吧~实用第一哈。