Android一键更换主题套件

前言

前阵子看了bilibili上的一些技术相关影片,码牛学院的公开课程
Android动态加载技术的高级进阶,手写实现网易云主题换肤框架

影片中的讲者用Java初步实作了一个修改主题的框架
学习的过程动作做是个满重要的阶段
因此我把影片中的这个框架跟着教学手刻一次

刻完套用在我自己的测试专案上时发现还有些问题
接着就依照遇到的问题进行进一步的修改

在教学影片中看得到的逻辑规则,本篇文章就不赘述,我会直接说明我基于教学的框架增加或修改的部分
不想浪费时间看文章可以直接看我的Repository ChangeThemeSample

问题集

- 第一个遇到的问题是,依照我过去的开发习惯,专案经常会大量的使用到style来设定相同规格的物件

例如这样统一设定TextView的风格

<style name="style_button_text">    <item name="android:layout_width">match_parent</item>    <item name="android:layout_height">wrap_content</item>    <item name="android:textColor">@color/white</item>    <item name="android:layout_gravity">center</item>    <item name="android:gravity">center</item>    <item name="android:background">@drawable/selector_btn_circle_default</item>    <item name="android:paddingBottom">10dp</item></style> <TextView    style="@style/style_button_text" />

在使用style的情况下,透过AttributeSet getAttributeName只会取到"style"
而style_button_text里面有设定的textColor, background是取不到的
这样在后续设定主题时,使用style的写法就会失效

因此需要针对style的况状更深入的处理
首先我定义了,方便foreach确认属性是否存在

private static final int[] NATIVE_ATTRIBUTE_ID = {        android.R.attr.textColor,        android.R.attr.background,        android.R.attr.src};

逐一确认style中是否能够取到我们对应的属性

TypedArray typedArray = view.getContext().obtainStyledAttributes(attrs.getStyleAttribute(), NATIVE_ATTRIBUTE_ID);if (typedArray.length() > 0){    for(int ti = 0; ti < typedArray.length(); ti++){        try{            if(typedArray.hasValue(ti)){                int resId = typedArray.getResourceId(ti, -1);                if(resId != -1) {                    addSkinItem(view, NATIVE_ATTRIBUTE_NAME[ti], resId, skinItems);                }            }        }        catch (Exception e) {            e.printStackTrace();        }    }}typedArray.recycle();

这边有遇到一个问题还没深入去找原因,NATIVE_ATTRIBUTE_ID中将textColor及background顺序调换后,textColor会失效取不到resourceId,之后有空必须要查查

- 第二个问题是,不支援专案正在使用的物件或属性

解决方案当然就是要把物件及相应的属性也加入到判断的清单

private static final String[] THIRDPARTY_ATTRIBUTE_NAME = {        "tabIndicator",        "tabIndicatorColor"};private static final String[] THIRDPARTY_VIEW = {        "com.google.android.material.tabs.TabLayout"};

利用反射的方式将数值置换

Method setSelectedTabIndicator = view.getClass().getDeclaredMethod("setSelectedTabIndicator", Drawable.class);                        setSelectedTabIndicator.invoke(view, SkinManager.getInstance().getDrawable(skinItem.getResId()));

- 第三个问题则是由第二个问题衍伸

Android的物件、属性千百种,实在不太可能每一个都加入我们的判断规则中,就算都整理进来多少也会有效能上的问题,例如每个onCreateView都要foreach跑一次包含2000项目的array大概受不了

因此需要增加一个可以让人扩充判断及设定的功能
这边以ProgressBar及progressDrawable属性作为範例
首先需要生成一个callback(CustomViewAttributeApplyListener),这个callback会将当下要置换主题的view、属性及目前取到的资源回传,开发人员收到此回传实再进行相对应的设定

这边比较需要注意点的点是,进行设定要取得资源时必须透过SkinManager中的getDrawable、getColor相关方法取得,如果直接使用当下的conetext取回资源则会取到原始APK的资源,而非主题包

CustomViewAttributeApplyListener listener = (view, fieldName, resId) -> {    String viewName = view.getClass().getSimpleName();    switch (viewName){    case "ProgressBar":        ((ProgressBar)view.findViewById(R.id.progressBar)).setProgressDrawable(SkinManager.getInstance().getDrawable(resId));        break;    }};SkinManager.getInstance().addCustomView(new SkinCustomView(ProgressBar.class.getSimpleName(), ProgressBar.class.getName(), new String[]{"progressDrawable"}, listener));

- 最后一个问题

大概是每个专案都一定会遇到的状况,某些UI上效果需要依照API回传的状态呈现
例如状态1文字使用颜色A、背景使用AA;状态2文字使用颜色B、背景使用BB

这个状况再设定时就需要参考问题三
同样是使用SkinManager中的getDrawable、getColor相关方法取得资源

总结

个人认为看完公开课程还算满有收穫的,也算是有个现成的可以学习,也讲解得满详细的,后面进阶课销售相关的话真的很多。不过也可以透过他讲述的一些状况了解内地的行情可能也不是坏事

这个套件肯定还有许多需要优化的地方,以及Bug需要修改,有任何建议欢迎提出或加入一起维护


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章