Android 主题切换

xiaoxiao2021-02-28  92

介绍

所谓的多主题切换,就是能够根据不同的设定,呈现不同风格的界面给用户。想实现Android多套主题的切换,网络上方案已经很多了,也看了许多大神的实现方式,但心里总想着自己去实现一遍,就这么借鉴GitHub的开源实现了一个简单的Android换肤框架。

实现的思路

通过LayoutInflaterCompat.setFactory方式,在onCreateView的回调中,解析每一个View的attrs, 判断是否有已标记需要换肤的属性, 比方说background, textColor, 或者说相应资源是否为skin_开头等等.然后保存到集合中, 将相应的属性收集到一起。 这种方式相对是比较简单的,易于实现的方式。于是我也采用了这种方式去捉摸一番。

最后实现的效果

项目地址: https://github.com/zguop/Towards 存在于wt_library下 theme包中。

一张图了解Android中的主题颜色

我们可以根据这里的颜色定义成多套的主题Style,来应用我们的Android应用。

主题实现

public void init(final AppCompatActivity activity) { LayoutInflaterCompat.setFactory(LayoutInflater.from(activity), new LayoutInflaterFactory() { @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { List<SkinAttr> skinAttrsList = getSkinAttrs(attrs, context); //如果属性为null 并且名字没有包含. 说明不是自定义的view if (skinAttrsList == null || skinAttrsList.isEmpty()) { if (!name.contains(".")) { return null; } } View view = activity.getDelegate().createView(parent, name, activity, attrs); if (view == null) { view = createViewFromTag(context, name, attrs); } if (view != null) { if (skinAttrsList == null && !(view instanceof SkinCompatSupportable)) { return null; } mSkinViewList.add(new SkinView(view, skinAttrsList)); } return view; } }); }

oncreate中会回调当前页面中的view名称及view中所使用到的属性, 该方法主要是获取到视图中,符合条件换肤的view,和需要变更的xml属性,保存到集合中。 关于LayoutInflaterCompat.setFactory的作用,这里有一篇文章讲解 http://blog.csdn.net/lmj623565791/article/details/51503977

List<SkinAttr> skinAttrsList = getSkinAttrs(attrs, context);

首先我们通过getSkinAttrs()方法获取到一个view中需要换肤的属性集合,

private static final String COLOR_PRIMARY = "colorPrimary"; private static final String COLOR_PRIMARY_DARK = "colorPrimaryDark"; private static final String COLOR_ACCENT = "colorAccent"; private static final String ATTR_PREFIX = "skin"; //开头 private List<SkinAttr> getSkinAttrs(AttributeSet attrs, Context context) { List<SkinAttr> skinAttrsList = null; SkinAttr skinAttr; //遍历所有属性 for (int i = 0; i < attrs.getAttributeCount(); i++) { //获取到当前的属性名字, String attributeName = attrs.getAttributeName(i); //改方法获取到枚举中定义好的需要更改的属性进行匹配 SkinAttrType attrType = getSupportAttrType(attributeName); if (attrType == null) { continue; } //获取当前属性对应的值 并解析,如果是使用?attr/ 或者是 @color/属性 String attributeValue = attrs.getAttributeValue(i); if (attributeValue.startsWith("?") || attributeValue.startsWith("@")) { //获取到该资源的id int id = ThemeUtils.getAttrResId(attributeValue); if (id != 0) { //通过资源id 获取到资源的名称 String entryName = context.getResources().getResourceEntryName(id); //如果匹配 资源名称 表示都是使用了换肤的 属性则保存起来 if (entryName.equals(COLOR_PRIMARY) || entryName.equals(COLOR_PRIMARY_DARK) || entryName.equals(COLOR_ACCENT) || entryName.startsWith(ATTR_PREFIX)) { if (skinAttrsList == null) { skinAttrsList = new ArrayList<>(); } String typeName = context.getResources().getResourceTypeName(id); skinAttr = new SkinAttr(attrType, entryName, attributeName, typeName); skinAttrsList.add(skinAttr); } } } } return skinAttrsList; }

遍历所有属性,获取每一个属性的名字,通过getSupportAttrType()方法进行属性匹配,

private SkinAttrType getSupportAttrType(String attrName) { for (SkinAttrType attrType : SkinAttrType.values()) { if (attrType.getAttrType().equals(attrName)) return attrType; } return null; }

实现定义好一个需要换肤的一些属性枚举,这里定义了三个枚举值,当前大家也可以根据自己的需要定义更多的属性。background背景色,textColor字体颜色,src图片,当遍历的属性刚好是以下属性的时候,那么就认为是一个有效的,需要换肤的view。

enum SkinAttrType { BACKGROUD("background") { @Override public void apply(View view, String attrName, String attrValueResName, String attrValueTypeName) { Context context = view.getContext(); view.setBackground(ThemeUtils.getDrawable(context, getResId(context, attrName))); } }, COLOR("textColor") { @Override public void apply(View view, String attrName, String attrValueResName, String attrValueTypeName) { Context context = view.getContext(); ((TextView) view).setTextColor(ThemeUtils.getColorStateList(context, getResId(context, attrName))); } }, SRC("src") { @Override public void apply(View view, String attrName, String attrValueResName, String attrValueTypeName) { Context context = view.getContext(); ((ImageView) view).setImageDrawable(ThemeUtils.getDrawable(context, getResId(context, attrName))); } } }

枚举中定义了一个抽象方法

public abstract void apply(View view, String attrName, String attrValueResName, String attrValueTypeName);

用于最后的更换颜色调用apply方法的实现。

匹配换肤属性的规则:定义好匹配的属性 这里选择了 系统的 三种 colorPrimary 名字 或者是 skin开头的,这里的资源属性命名有需要规范了,例如需要background背景色,那么?attr/colorPrimary使用或者是 @color/skin_bottom_bar_not skin命名开头的资源,就认定为时需要换肤的属性值。

实例 app:background="?attr/colorPrimary" app:background="@drawable/skin_bottom_bar_not"

获取到每一条有效的换肤属性,保存到集合中返回。 这里就是将所有需要换肤的属性返回来了,属性有 了,那么还需要生成对应的view,最后更换主题的时候去设置view所需要改变的颜色。

回到onCreateView方法中

View view = activity.getDelegate().createView(parent, name, activity, attrs); if (view == null) { view = createViewFromTag(context, name, attrs); } if (view != null) { if (skinAttrsList == null && !(view instanceof SkinCompatSupportable)) { return null; } mSkinViewList.add(new SkinView(view, skinAttrsList)); } return view;

如果有存在需要换肤的属性集合,才会去创建该view,保存到一个最终的集合中。这里就完成了一个初始化的过程,获取所以需要换肤的view,保存起来。那么接下就可以变更主题了。对外提供了两个方法

* 装载改变主题的view 及 需要改变的主题元素 public class SkinView { private View view; private List<SkinAttr> attrs; public SkinView(View view, List<SkinAttr> skinAttrs) { this.view = view; this.attrs = skinAttrs; } public void apply() { if (view == null) { return; } if (view instanceof SkinCompatSupportable) { ((SkinCompatSupportable) view).applySkin(); } if (attrs == null) { return; } for (SkinAttr attr : attrs) { attr.apply(view); } } } /** * 设置当前主题 */ public void setTheme(Activity ctx) { int theme = USharedPref.get().getInteger(PRE_THEME_MODEL); ThemeEnum themeEnum = ThemeEnum.valueOf(theme); ctx.setTheme(themeEnum.getTheme()); } /** * 更改主题 */ public void changeNight(Activity ctx, ThemeEnum themeEnum) { ctx.setTheme(themeEnum.getTheme()); showAnimation(ctx); refreshUI(ctx); USharedPref.get().put(PRE_THEME_MODEL, themeEnum.getTheme()); }

设置主题和更改当前的主题,当然我们还需要在style中定义多套主题。

蓝色调主题 <style name="AppThemeBlue" parent="AppTheme"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorBlue</item> <item name="colorPrimaryDark">@color/colorBlue</item> <item name="colorAccent">@color/colorBlue</item> <!--白色字体--> <item name="skin_kind_color_not">@color/colorWhite</item> <!--相近暗淡色--> <item name="skin_home_title_not">@color/home_title3</item> <!--dialog背景色--> <item name="skin_dialog_bg_not">@color/color_ef6f26</item> <!--相反色--> <item name="skin_contrast_color_not">@color/color_ef6f26</item> <!--主题透明度色--> <item name="skin_transparent_theme_color">@color/color_blue_80</item> <!--字体暗色--> <item name="skin_text_dark_color">@color/color_373737</item> <!--灰色字体--> <item name="skin_text_gray_color">@color/color_a7a7a7</item> <!--背景图片--> <item name="skin_Background_drawable">@drawable/theme_bg3</item> </style> 红色调主题 <style name="AppThemeRed" parent="AppTheme"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorRed</item> <item name="colorPrimaryDark">@color/colorRed</item> <item name="colorAccent">@color/colorRed</item> <item name="skin_kind_color_not">@color/colorWhite</item> <item name="skin_home_title_not">@color/home_title2</item> <item name="skin_dialog_bg_not">@color/color_31c2c5</item> <item name="skin_contrast_color_not">@color/color_31c2c5</item> <item name="skin_transparent_theme_color">@color/color_red_80</item> <item name="skin_text_dark_color">@color/color_373737</item> <item name="skin_text_gray_color">@color/color_a7a7a7</item> <item name="skin_Background_drawable">@drawable/theme_bg1</item> </style>

多套主题的定义,当调用更改主题时

/** * 更改主题 */ public void changeNight(Activity ctx, ThemeEnum themeEnum) { ctx.setTheme(themeEnum.getTheme()); showAnimation(ctx); refreshUI(ctx); USharedPref.get().put(PRE_THEME_MODEL, themeEnum.getTheme()); }

开始了一个过渡动画,然后进行页面的UI刷新,

/** * 刷新UI界面 */ private void refreshUI(Activity ctx) { refreshStatusBar(ctx); for (SkinView skinView : mSkinViewList) { skinView.apply(); } } public void apply() { if (view == null) { return; } if (view instanceof SkinCompatSupportable) { ((SkinCompatSupportable) view).applySkin(); } if (attrs == null) { return; } for (SkinAttr attr : attrs) { attr.apply(view); } }

继续调用枚举中apply这个抽象方法

public void apply(View view) { attrType.apply(view, attrName, attrValueResName, attrValueTypeName); }

每个属性下实现各自的apply更改UI的实现

背景色的修改 BACKGROUD("background") { @Override public void apply(View view, String attrName, String attrValueResName, String attrValueTypeName) { Context context = view.getContext(); view.setBackground(ThemeUtils.getDrawable(context, getResId(context, attrName))); } }, 字体颜色 COLOR("textColor") { @Override public void apply(View view, String attrName, String attrValueResName, String attrValueTypeName) { Context context = view.getContext(); ((TextView) view).setTextColor(ThemeUtils.getColorStateList(context, getResId(context, attrName))); } }, 图片 SRC("src") { @Override public void apply(View view, String attrName, String attrValueResName, String attrValueTypeName) { Context context = view.getContext(); ((ImageView) view).setImageDrawable(ThemeUtils.getDrawable(context, getResId(context, attrName))); } },

这样一个简单的换肤就实现完成啦,具体的细节可以到代码中查看。 https://github.com/zguop/Towards 存在于wt_library下 theme包中。

关于自定义view设置的自定义属性设置换肤属性,可以实现SkinCompatSupportable接口来更改自定义属性的,来调用自己view中的方法。

public interface SkinCompatSupportable { void applySkin(); }
转载请注明原文地址: https://www.6miu.com/read-40925.html

最新回复(0)