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());}
}// 添加了测试的 背景颜色与文本颜色
// 切换主题
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
// 默认主题使用
// NewAppTheme 使用
// 测试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. 但是前提是你需要定义好你自己的 相关值,比如阴影多少,圆角的角度,一级文本,二级文本属性等等,这里可以称为 通用属性
.
// 布局里面使用
android:textColor="?attr/tvui_text_color"
// 代码中使用
ColorStateList textColor = SkinManager.getAttrColorStateList(getContext(), R.attr.tvui_text_color);
「这套主题的优/缺点:」
当然,因为某些局限性,肯定使用场景不同. 如果你是那种需要下载以及加载各种 酷炫主题包的,这种方案肯定不适合你. 如果只是UI库统一配置(新的UI界面,只需要再原来的基础继承,就可以产生一套新的),只有亮/暗主题切换,简单,方便.
「参考资料:
」 Android如何在代码中获取attr属性的值
Andrid-Skin-Loader 主题切换方案
git项目地址:
Andrid-Skin-Loader 流程步骤:
「加载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 相关函数
」
// 获取颜色值 比如 #ecf0f1
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资源里面是这样:#FF0000 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); 获取相关资源. 具体代码参考:
DEMO代码
「优缺点探讨:
」
「参考资料:
」 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
参考资料
LayoutInflater.SetFactory()学习(2)
QMUI换肤方案
Android-Skin-Loader
Android系统资源访问机制的探讨,小米科技董红光:Android系统如何实现换肤
Android 资源加载机制详解
Android 换肤那些事儿, Resource包装流 ?AssetManager替换流?
《Android插件化开发》,《深入探索安卓热修复技术原理》,《Android 全埋点解决方案》
Turtlesim 包的相关命令 这个部分包含五个子主题,分别是 列出所有活动节点列出所有主题获取有关主题的信息显示消息信息实时回应消息 1.列出所有的活动节点 为了获取所用的活动且向ROS Master注册的节点,我们使用命令 rosnode list 运行如下 我们可以看到有三个活动且注册的节点显示在ROS界面上,...
Ctrl+K Ctrl+M设置 Ctrk+K Ctrl+T选择这个绿茶婊主题怎么样? 对图标样式有洁癖的可以选择上面这个图标主题...
上图(做图是原生的主题,有图是自定义的颜色主题) 仔细看才发现右侧的顶部那个复选框靠左了,百思不得其解,尝试了各种骚操作无果 于是拿着新的主题样式index.css和老的比对发现他多了一段代码(左边是原生主题样式,右边是自定义主题样式) 于是去掉了padding-left和padding-righ...
什么是Vuex?只需三分钟!只需创建一个vuex.js文件,让你马上学会使用Vuex,尽管Vuex是个鸡肋!(扔掉store文件夹和里面的index、getters、actions、mutations等js文件吧!)_你挚爱的强哥❤给你发来1条消息❤-CSDN博客https://s-z-q.blog.csdn.net/article/...
app.component.html
grid: {show: false,left: '0px',top: '50px',right: '1px',bottom: '0px',containLabel: true,backgroundColor: 'white',//show: true的时候才显示borderColor: '#ccc',borderWidth: 1,/...
/*横纵分割线颜色透明度*/ xAxis: {axisLabel: {textStyle: {color: "#2484db"}},axisLine: {lineStyle: {color: 'rgba(0,2,85,1)',width: 2,}},splitLine: {show: true,lineStyle: {colo...
引言 在这个-SLAM建图和导航仿真实例-项目中,主要分为三个部分,分别是 (一)模型构建(二)根据已知地图进行定位和导航(三)使用RTAB-MAP进行建图和导航 该项目的slam_bot已经上传我的Github。 这是第三部分,完成效果如下 图1 建图和导航 三、使用RTAB-Map进行建图和导航 1. rtab...
引言 在这个-SLAM建图和导航仿真实例-项目中,主要分为三个部分,分别是 (一)模型构建(二)根据已知地图进行定位和导航(三)使用RTAB-MAP进行建图和导航 该项目的slam_bot已经上传我的Github。 由于之前的虚拟机性能限制,我在这个项目中使用了新的ubantu 16.04环境,虚拟机配置 内存 8GCPU...
[{name:1},{name:2}].forEach((v,i,ar) => {console.log(v,i,ar)});//基础遍历[{name:1},{name:2}].map((v) => v.name);//[1,2]返回对象数组中指定字段值的一位数组(不改变原始数组)[{name:1},{name:2},{name:3}...
体验内容 使用gmapping方法利用turtlebot底盘移动信息和激光雷达数据进行建图。 1. 安装一些依赖包 sudo apt-get install ros-melodic-move-base* sudo apt-get install ros-melodic-map-server* sudo apt-get insta...
前言 我们知道Java/Python这种语言能够很好得 支持反射。反射机制 就是一种用户输入的字符串到对应实现方法的映射,比如http接口中 用户传入了url,我们需要调用该url对应的方法/函数对象 从而做出对应的操作。 而C++ 并没有友好得支持这样的操作,而最近工作中需要通过C++实现http接口,这个过程想要代码实现得优雅...