写在前面

Android TV 电视开发,主题换肤,我感觉有两种层级的方式,一种是 系统级,另一种 是应用级,

我记得很早在 Linux 桌面开发的时候,我们之前的公司在GTK+上也实现了一套换肤UI框架,

包括我们看到的QQ,掘金,简书,等应用,无缝切入主题,不需要重启应用。

为何需要主题切换,因为UI库改来改去也就那些东西,不过在变换一些样式,阴影,圆角,文本大小,颜色,边框,背景 等等而已。

那么其实我们可以通用主题的配置方式,统一配置更改样式.

几种方案探讨

下面探讨的方案,都是不需要重启应用,及时生效的方案。 主题切换的问题在于,新的出来的界面,还有已经存在的界面。

QMUI 主题换肤方案

QMUI的方案,定义各种 theme style,通过遍历已存在的View,然后设置相关属性,并且设置 Theme.;包括后续新出的控件也是已切换的主题样式.

public void changeSkin(Object ob, int skinRes, Resources resources) {View view = null;if (null != ob) {if (ob instanceof Activity) { // 普通的 Activity支持Activity activity = (Activity) ob;Drawable drawable = getAttrDrawable(activity, activity.getTheme(), R.attr.tvui_commonItem_detailColor);activity.getWindow().setBackgroundDrawable(drawable);// 获取 content view.view = activity.findViewById(android.R.id.content);} else if (ob instanceof Dialog) { // 系统设置5.0 - Dialog 主题切换支持Window window = ((Dialog) ob).getWindow();if (window != null) {view = window.getDecorView();}} else if (ob instanceof View) { // 普通的 View 主题切换支持view = (View) ob;}mResources = resources != null ? resources : view.getResources();if (null != view) {// 切换主题样式theme = mResources.newTheme();theme.applyStyle(skinRes, true);// 设置背景颜色ColorStateList bgColorStateList = getAttrColorStateList(mContext, theme, R.attr.tvui_main_layout_background_color);view.setBackgroundColor(bgColorStateList.getDefaultColor());// 切换主题样式,遍历所有子View...setThemeDatas(view, skinRes, theme);}}
}// 遍历Views切换主题样式
private void setThemeDatas(View view, int skinRes, Resources.Theme theme) {applyTheme(view, skinRes, theme);if (view instanceof ViewGroup) {ViewGroup viewGroup = (ViewGroup) view;for (int i = 0; i < viewGroup.getChildCount(); i++) {setThemeDatas(viewGroup.getChildAt(i), skinRes, theme);}}
}private void applyTheme(View view, int skinRes, Resources.Theme theme) {if (view instanceof Button) {((Button)view).setTextColor(getAttrColorStateList(mContext, theme, R.attr.tvui_text_color).getDefaultColor());}
}// 添加了测试的 背景颜色与文本颜色
<attr name="tvui_main_layout_background_color" format="color"/>
<attr name="tvui_text_color" format="color"/><style name="NewAppTheme" parent="xxxx"><item name="tvui_main_layout_background_color">#FF0000</item><item name="tvui_text_color">#0000FF</item>
</style>// 切换主题
SkinManager.getInstance(AnimHomeActivity.this).changeSkin(AnimHomeActivity.this, R.style.NewAppTheme);

切换主题效果如下

主题切换示例代码

上面会存在一个问题,已经切换了主题样式,新出来的界面控件这么办??????? 为了使后续启动的 新出来的界面控件也是切换后的主题,需要作出如下修改

public void changeSkin(Object ob, int skinRes, Resources resources) {View view = null;... ...view.setBackgroundColor(bgColorStateList.getDefaultColor());// 新增 随意一个都可以,设置后,后续新出来的控件都是现在切换的主题样式view.getContext().setTheme(skinRes);//view.getContext().getTheme().setTo(theme);
}// 主题 style
<attr name="TVUIRoundButtonStyle" format="reference"/>
<declare-styleable name="UIRoundButton"><attr name="tvui_text_color"/>
</declare-styleable>// 默认主题使用
<style name="TVUITheme.UIRoundButton"><item name="tvui_text_color">#FFEB3B</item>
</style>// NewAppTheme 使用
<style name="TVUITheme.UIRoundButton.New"><item name="tvui_text_color">#2196F3</item>
</style><style name="NewAppTheme" parent="xxxxx"><item name="tvui_main_layout_background_color">#FF0000</item><item name="tvui_text_color">#0000FF</item><item name="TVUIRoundButtonStyle">@style/TVUITheme.UIRoundButton.New</item>
</style>// 测试UIButton代码
public class TVUIButton extends Button {public TVUIButton(Context context) {this(context, null);}public TVUIButton(Context context, AttributeSet attrs) {this(context, attrs, R.attr.TVUIRoundButtonStyle);}public TVUIButton(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.UIRoundButton, defStyleAttr, 0);int textColor = ta.getColor(R.styleable.UIRoundButton_tvui_text_color, Color.BLACK);setTextColor(textColor);setTextSize(152);ta.recycle();}
}

新增控件的效果

新增的相关代码

补充,如果感觉麻烦,也可以直接在布局里面使用 attr 的属性,就不需要定义一堆的和button相关的Style. 但是前提是你需要定义好你自己的 相关值,比如阴影多少,圆角的角度,一级文本,二级文本属性等等,这里可以称为 通用属性.

<attr name="tvui_text_color" format="color"/><style name="NewAppTheme" parent="xxxx"><item name="tvui_text_color">#0000FF</item>
</style>// 布局里面使用
android:textColor="?attr/tvui_text_color"
// 代码中使用
ColorStateList textColor = SkinManager.getAttrColorStateList(getContext(), R.attr.tvui_text_color);

「这套主题的优/缺点:」

  • 简单方便,适用于 只有亮/暗的主题切换.
  • 缺点也明显,如果要新增主题,就要更改或者新增,很麻烦,会导致重新发布APK.

当然,因为某些局限性,肯定使用场景不同. 如果你是那种需要下载以及加载各种 酷炫主题包的,这种方案肯定不适合你. 如果只是UI库统一配置(新的UI界面,只需要再原来的基础继承,就可以产生一套新的),只有亮/暗主题切换,简单,方便.

参考资料: Android如何在代码中获取attr属性的值

Andrid-Skin-Loader 主题切换方案

git项目地址:

Andrid-Skin-Loader 流程步骤:

  • 初始化加载APK主题包
  • 通用 Factory 遍历 View,保存相关View与属性的信息
  • 最后切换主题的时候,重新调用 SkinManager 来获取相关主题包的资源属性.

加载APK主题包

// 这里会有点卡,建议放在异步线程里面执行相关操作
String skinPkgPath = "皮肤的apk路径";
File file = new File(skinPkgPath);
if(file == null || !file.exists()){return null;
}PackageManager mPm = context.getPackageManager();
// getPackageArchiveInfo文章: https://blog.csdn.net/qq379454816/article/details/50402805?locationNum=10&fps=1
PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
skinPackageName = mInfo.packageName;AssetManager assetManager = AssetManager.class.newInstance();
// 调用addAssetPath方法将皮肤包加载进内存
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);Resources superRes = context.getResources();
Resources skinResource = new Resources(assetManager, superRes.getDisplayMetrics(),superRes.getConfiguration()); // apk皮肤包的资源句柄skinPath = skinPkgPath;
isDefaultSkin = false;return skinResource;

Factory 遍历 View,保存相关view信息 因为做主题切换的,已经显示出来的没有办法更新,所以需要保存相关信息,切换的时候,就更新这些view的信息,达到主题切换的效果

public class SkinInflaterFactory implements Factory {public View onCreateView(String name, Context context, AttributeSet attrs) {view = LayoutInflater.from(context).createView(name, "android.view.", attrs);// 创建 view.view = LayoutInflater.from(context).createView(name, null, attrs);// 这里可以判断 view 的 name,是不是 原生的,你也可以替换成你的//return view;}// 解析view的 attrs 的属性,保存view的相关信息private void parseSkinAttr(Context context, AttributeSet attrs, View view) {}
}// Activity或者其它地方如何使用??
getLayoutInflater().setFactory(new SkinInflaterFactory());

但是如果要达到遍历View的效果,也可以参考 changeSkin,不一定要保存相关信息的

SkinManager 相关函数

// 获取颜色值 比如 <color name="tvui_bg_color">#ecf0f1</color>
public int getColor(int resId){int originColor = context.getResources().getColor(resId);// 如果APK资源 mResources为空 或 使用默认的,就返回当前APP默认的资源if(mResources == null || isDefaultSkin) {return originColor;}// 获取 ID 的字符串名称  ,这里返回的是 tvui_bg_colorString resName = context.getResources().getResourceEntryName(resId);// getResourceTypeName(resId) 获取 color, drawable 等类型,这里返回的就是 color// 通过APK资源的 mResources 获取APK 相关的资源.(类型,名字相关,值不一样的)// 比如APK资源里面是这样:<color name="tvui_bg_color">#FF0000</color>int trueResId = mResources.getIdentifier(resName, "color", skinPackageName);int trueColor = 0;try{trueColor = mResources.getColor(trueResId);} catch(NotFoundException e) {e.printStackTrace();trueColor = originColor;}return trueColor;
}
// 获取Drawble
getDrawable... ...

上面已经提到当前已经显示的,那么后面新出来的控件这么办?这个就需要再代码里面编写相关代码才行.

有人肯定会想,我将 Activity 的 getResources 变成 APK资源的 resources 不就完啦,哈哈哈,肯定不是这样的.

 public Resources getResources() {Resources resources = SkinManagerApk.getInstance().getResources();if (null != resources) {return resources;}return super.getResources();
}
// getResources().getColorStateList(R.color.news_item_text_color_selector);

不能以上面的方式获取,因为 getColorStateList(... ...); 这个资源的ID是本APK的,不是主题资源APK里面的.那要这么办? 只能使用 SkinManager getColor 类似的方式. 通用 getResourceEntryName 获取到 news_item_text_color_selector 的名称. 然后再 通用 mResources.getIdentifier("news_item_text_color_selector", "color", skinPackageName); 获取资源APK的ID. 最后通用 mResources.getColorStateList(trueResId); 获取相关资源. 具体代码参考:

  • Andrid-Skin-Loader 的 SkinManager
  • 也可以参考我缩减的代码 SkinMangerApk

DEMO代码

优缺点探讨:

  • 可以进行主题包下载,多主题更新,非常方便
  • 局限性就是 你再 XML布局写的默认的,下次显示出来还是原来默认的,就需要再代码里面进行 已经切换主题的 资源获取,用代码填充上去,侵入式的.
  • 每次都要重新加载APK资源包

参考资料: Android换肤原理和Android-Skin-Loader框架解析

热更新资源替换方案

我们知道,系统级的替换主题都是 替换的资源ID. 那我们思考一下,能不能有一种主题切换方式,我们加载的主题APK资源包,就能替换当前APP默认的资源,我们不用手写代码去填写覆盖?也能及时更新资源?

按照热更新的例子,资源是可以替换的.

相关代码: https://gitee.com/hailongqiu/OpenDemo/tree/master/app/src/main/java/com/open/demo/skin/instant

参考资料: https://www.cnblogs.com/yyangblog/p/6252490.html

AOP方案切换主题

经过我查询,原来可以替换 resource,做一个钩子;那么我们获取到APK资源的 resource,替换掉其它相关的 resource 应该就可以达到这样的效果。包括布局XML的,新出来的等等... ..

代理 View.OnClick... Window.Callback 相关函数,钩子...

View.AccessibilityDelegate

ASM,AST

Android AOP三剑客:APT, AspectJ 和 Javassist

  • APT应用:Dagger,butterKnife,组件化方案等等
  • AspectJ:主要用于性能监控,日志埋点等
  • Javassist:热更新(可以在编译后,打包Dex之前干事情,可以突破一下限制)

参考资料

LayoutInflater.SetFactory()学习(2)

QMUI换肤方案

Android-Skin-Loader

Android系统资源访问机制的探讨,小米科技董红光:Android系统如何实现换肤

Android 资源加载机制详解

Android 换肤那些事儿, Resource包装流 ?AssetManager替换流?

《Android插件化开发》,《深入探索安卓热修复技术原理》,《Android 全埋点解决方案》

android view设置按钮颜色_Android 主题换肤技术方案分析相关推荐

  1. Android 主题换肤技术方案分析

    写在前面 Android TV 电视开发,主题换肤,我感觉有两种层级的方式,一种是 系统级,另一种 是应用级, 我记得很早在 Linux 桌面开发的时候,我们之前的公司在GTK+上也实现了一套换肤UI ...

  2. android view设置按钮颜色_Android关于StatusBar(状态栏)总结

    从事Android开发的同学了解,对于StatusBar的控制,并没有ios那样好,需要对Android不同的系统版本进行适配,同时也需要对小米,魅族等国产手机单独适配,想要如ios那般好的体验,并没 ...

  3. android view设置按钮颜色_Android 酷炫自定义 View:高仿 QQ 窗帘菜单

    作者:大公爵 链接:https://www.jianshu.com/p/cdb3d373fe37 介绍 不知道大家是否有印象,QQ 曾经有个版本用到了一种双向侧拉菜单,就像窗帘一样可以两边开合,并且伴 ...

  4. android view设置按钮颜色_建议收藏!最全 Android 常用开源库总结!

    作者 |  i小灰地址 |  https://www.jianshu.com/p/3fde87405411 前言 收集了一些比较常见的开源库,特此记录(已收录350+).另外,本文将持续更新,大家有关 ...

  5. 组件库自定义主题换肤实现方案

    概述 douluo-ui 组件库 是基于 element-ui 实现的,那么实现换肤分两步:一是 element-ui 的换肤方案:二是 douluo-ui组件库 的换肤方案 element-ui 的 ...

  6. android开发按钮颜色,Android编程实现简单设置按钮颜色的方法

    本文实例讲述了Android编程实现简单设置按钮颜色的方法.分享给大家供大家参考,具体如下: 1.工程目录 a.在res目录-新建drawble文件夹放入自定义图片 2.main.xml androi ...

  7. android 布局颜色设置颜色设置,怎么在Android中利用view设置布局颜色

    怎么在Android中利用view设置布局颜色 发布时间:2020-12-10 16:16:37 来源:亿速云 阅读:154 作者:Leah 这期内容当中小编将会给大家带来有关怎么在Android中利 ...

  8. android代码设置弹窗颜色,Android编程实现简单设置按钮颜色的方法

    本文实例讲述了Android编程实现简单设置按钮颜色的方法.分享给大家供大家参考,具体如下: 1.工程目录 a.在res目录-新建drawble文件夹放入自定义图片 2.main.xml androi ...

  9. Android主题换肤 无缝切换

    作者 _SOLID 关注 2016.04.17 22:04* 字数 4291 阅读 23224评论 123喜欢 679 今天再给大家带来一篇干货. Android的主题换肤 ,可插件化提供皮肤包,无需 ...

最新文章

  1. STARTUPE2原语
  2. 有比 ReadWriteLock更快的锁?
  3. 8个应该了解的CSS3技术
  4. [UWP]涨姿势UWP源码——Unit Test
  5. JVM系列之:从汇编角度分析Volatile
  6. Effective Java之使可变性最小(十五)
  7. Dalivik垃圾回收收机制Cocurrent GC简介
  8. C#实现人脸识别【Users】
  9. 快速完成和读懂测试计划
  10. 最速下降法python_最速下降法python实现
  11. 51单片机引脚内部电路
  12. 芝诺数解|「五」千言万语,都在锅里——重庆火锅
  13. 国内各省市有关中小学少儿编程进展(节选)
  14. sam账号服务器已断开连接,删了sam文件后引发的IIS问题的解决方法
  15. Android——广播
  16. matlab dfe 仿真,Matlab Simulink
  17. 计算机连接无线网络的步骤,笔记本怎么连接wifi超详细教程
  18. Video.js使用教程(二)
  19. 纳豆红曲的功效与作用是什么?
  20. sql 中or与in的查询效率对比

热门文章

  1. 设置子节点相对于父节点水平竖直都居中
  2. string判断是否是正常的ip格式
  3. 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:禁用按钮
  4. Java--对象与类(三)
  5. Aurora的安装和中文配置
  6. (转载)(官网)UE4--Character
  7. 机器学习入门(12)— 激活函数层 ReLU、Sigmoid 层的实现
  8. 与后台交互方法一 ——Ajax
  9. LINUX下SVN命令大全
  10. ASP.NET性能调整之解决Server Too Busy错误