android 换肤框架搭建及使用 (3 完结篇)
本系列计划3篇:
- Android 换肤之资源(Resources)加载(一)
- setContentView() / LayoutInflater源码分析(二)
- 换肤框架搭建(三) — 本篇
tips: 本篇只说实现思路,以及使用,具体细节请下载代码查看!
本篇实现效果:
fragment换肤 | recyclerView换肤 | 自定义view属性换肤 |
---|---|---|
打开 | 打开 | 打开 |
动态换肤 | dialog换肤 | |
打开 | 打开 |
回顾
在第一篇中: 我们可以通过这段代码来创建自己的Resource来加载另一个apk中的资源
try (// 创建AssetManagerAssetManager assetManager = AssetManager.class.newInstance()) {// 反射调用 创建AssetManager#addAssetPathMethod method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);// 获取到当前apk在手机中的路径String path = getApplicationContext().getPackageResourcePath();/// 反射执行方法method.invoke(assetManager, path);// 创建自己的ResourcesResources resources = new Resources(assetManager, createDisplayMetrics(), createConfiguration());// 根据id来获取图片Drawable drawable = resources.getDrawable(R.drawable.ic_launcher_background, null);// 设置图片mImageView.setImageDrawable(drawable);} catch (Exception e) {e.printStackTrace();}// 这些关于屏幕的就用原来的就可以public DisplayMetrics createDisplayMetrics() {return getResources().getDisplayMetrics();}public Configuration createConfiguration() {return getResources().getConfiguration();}
在第二篇中: 我们分析了setContentView() 加载流程, 并且分析了LayoutInflater加载view流程
并且我们知道了如何通过Factory来拦截View创建
第二篇不是最近写的,是很早之前写的.这里正好适合,就当作第二篇来使用!
拦截代码:
class CustomParseActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {val layoutInflater = LayoutInflater.from(this)// 如果factory2 == null就创建if (layoutInflater.factory2 == null) {LayoutInflaterCompat.setFactory2(layoutInflater, object : LayoutInflater.Factory2 {// SystemAppCompatViewInflater 是粘贴自系统源码 [AppCompatViewInflater]val compatInflater = SystemAppCompatViewInflater()override fun onCreateView(parent: View?,name: String,context: Context,attrs: AttributeSet,): View? {// 在这里就可以拦截view的创建// Factory创建view val view = compatInflater.createView(parent, name, context, attrs, false,true, true, false)return view}... })}// 必须在super 之前super.onCreate(savedInstanceState)setContentView(activity_custom_parse)}}
项目搭建思路
要想达到换肤效果,其实就是加载另一个APK中的资源文件,然后实现替换
现在我们已经知道了如何加载另一个APK中的资源,我们只需要保存起来需要替换的view即可,然后再特定的时机去调用它
在点击换肤的时候,刷新所有保存的view对象,让它自己去加载另一个APK中的资源即可
首先我们需要规定替换哪些资源:
例如有一个view:
<Buttonandroid:id="@+id/bt1"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@color/global_background"android:text="@string/global_re_skin"android:textSize="@dimen/global_def_text_font"android:textColor="@color/global_text_color" />
这里我们就可以替换
- background
- text
- textSize
- textColor
因为这些属性是经常用的,并且是引用的资源文件中的资源,我想没人需要替换width / height
知道了需要替换哪些资源后,我们就可以在解析view的时候来保存起来这些属性,然后在某个时机的时候手动刷新即可
整个框架搭建我是采用的 Application.ActivityLifecycleCallbacks 这个类可以监听到activity所有的生命周期
并且采用了观察者设计模式,单例等设计模式,来实现点击的时候刷新需要改变属性的view
在使用的时候 只需要 一行代码就可以搞定
#Application.javapublic void onCreate(){SkinManager.init(this); }
在解析属性的时候,我采用了enum的特性 方便解析给view对应属性赋值
例如这样:
public enum SkinReplace {ANDROID_BACKGROUND("background") {@Overridevoid loadResource(View view, SkinAttr attr) {view.setBackgroundColor(XXX);}};private final String mName;SkinReplace(String value) {mName = value;}abstract void loadResource(View view, SkinAttr value);}
框架小细节
初始化factory
Application.ActivityLifecycleCallbacks#onActivityCreated() 执行时机为:
- AppCompatActivity.super.onCreate() 之后
- setContentView() 之前
我们由第二篇知道,Factory是在super.onCreate()中初始化的,并且Factory只能初始化一次,
在android28之前一般通过反射 LayoutInflater.mFactorySet 属性为false来实现加载我们的Factory
但是android28之后就不行了
那么android28之后版本我们可以通过反射来直接替换掉系统的Factory即可
// 通过反射替换掉系统的factoryprivate SkinLayoutInflaterFactory forceSetFactory2(LayoutInflater inflater, Activity activity) {Class<LayoutInflater> inflaterClass = LayoutInflater.class;try {String mFactoryStr = "mFactory";Field mFactory = inflaterClass.getDeclaredField(mFactoryStr);mFactory.setAccessible(true);String mFactory2Str = "mFactory2";Field mFactory2 = inflaterClass.getDeclaredField(mFactory2Str);mFactory2.setAccessible(true);SkinLayoutInflaterFactory skinLayoutInflaterFactory = new SkinLayoutInflaterFactory(activity);// 改变factorymFactory2.set(inflater, skinLayoutInflaterFactory);mFactory.set(inflater, skinLayoutInflaterFactory);return skinLayoutInflaterFactory;} catch (Exception e) {e.printStackTrace();}return null;}
一定创建View成功
我们粘贴出来 AppCompatViewInflater.java的时候,只能创建系统的view
我们必须创建view,因为我们需要通过view上的属性来判断它是否需要"换肤"
那么我们需要在这里的时候自己反射创建view[粘贴自LayoutInflater源码]
这里看不懂没关系,如果单纯的使用来说一点也不重要!
使用框架前提
- 有一个皮肤包, 在一篇中皮肤包如何制作我说的很详细了!
- 将皮肤包放入到手机内存中
- 记得读写权限,保证能够正常访问手机内存中的数据
- 引入lib-skin
- 在 Application.onCreate() 中初始化: SkinManager.init(this);
可以想像一下网易云,QQ等大厂的换肤, 点击一个按钮,然后下载一个皮肤包存储到手机中,然后我们去读取这个皮肤包的内容
最终我们只需要生成对应的皮肤包给到后台,然后我们就实现了动态的更换皮肤!
在Activity中换肤
如果你已经将皮肤包放入到了手机内存中,并且已经初始化了SkinManager
那么替换皮肤只需要一行代码:
SkinManager.getInstance().loadSkin("皮肤包的在手机中的路径",Activity);
如果你不想使用皮肤包,那么也只需要一行代码:
SkinManager.getInstance().reset();
现在你已经可以实现
- src
- text
- text_color
- text_size
- background
换肤了!
如果还需要其他属性换肤,下面会提到,别急!
在Fragment中使用换肤
在fragment中使用皮肤包只需要注意一点:
在view创建完成的时候调用:
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);SkinManager.getInstance().tryInitSkin(getActivity());
}
这是为了避免第一次初始化的时候加载不到皮肤
其他任何改变都不需要!
在RecyclerView中使用换肤
不需要任何处理
换肤:
SkinManager.getInstance().loadSkin("皮肤包的在手机中的路径",Activity); // 换肤
恢复默认:
SkinManager.getInstance().reset();
自定义属性换肤
首先我们需要随便自定义一个view
- 皮肤包中设置需要替换的资源
- 编写改变属性的方法:
4.在SkinReplace中规定需要改变的属性,并且通过反射调用对应方法
反射方法:
/** 作者:史大拿* 创建时间: 1/4/23 8:07 PM* TODO 自定义反射,反射具体方法属性* @param view: 需要反射的对象* @param methodName: 反射的方法名字* @param SkinReflectionMethod: 反射具体数据 [类型和参数]*/public void setCustomAttr(View view, String methodName, SkinReflectionMethod... data) {try {Class<?>[] cls = new Class<?>[data.length];Object[] objects = new Object[data.length];for (int i = 0; i < data.length; i++) {cls[i] = data[i].getCls();objects[i] = data[i].getObj();}Method method = view.getClass().getDeclaredMethod(methodName, cls);method.setAccessible(true);method.invoke(view, objects);} catch (Exception e) {e.printStackTrace();SkinLog.e("反射失败;" + e.getMessage() + "\t" + SkinConfig.SKIN_ERROR_7);}}
到此还是通过
SkinManager.getInstance().loadSkin(“皮肤包的在手机中的路径”,Activity);
换肤即可
动态换肤
动态换肤只需要在
SkinManager.getInstance().loadSkin(“皮肤包的在手机中的路径”,Activity);
之后调用对应方法即可
- drwable SkinManager.getInstance().getDrawable(String)
- string SkinManager.getInstance().getString(String)
- color SkinManager.getInstance().getColor(String)
- dimen SkinManager.getInstance().getFontSize(String)
例如这样:
findViewById(R.id.bt_re_skin).setOnClickListener(v -> {// 换肤SkinManager.getInstance().loadSkin(PATH,Activity);mTextView.setBackground(SkinManager.getInstance().getDrawable("global_skin_drawable_background"));mTextView.setText(SkinManager.getInstance().getString("global_custom_view_text"));});
如果app中有一个A资源, 皮肤包中没有A资源,现在已经换肤了 那么还是默认使用app中的A资源
但是如果app中没有A资源,并且皮肤包中也没有A资源,那么就报错了
就是一句话:
如果当前是换肤状态,那么优先使用皮肤包中的资源,
如果皮肤包中的资源不存在,则使用app中的资源,如果都不存在,那么就报错
Dialog换肤
AlertDialog
private AlertDialog alertDialog;private void showAlertDialog(View v) {// 避免重复解析皮肤包if (alertDialog == null) {View view = getLayoutInflater().inflate(R.layout.item_alert_dialog, null);alertDialog = new AlertDialog.Builder(this).setView(view).create();}if (!alertDialog.isShowing()) {alertDialog.show();}// 初始化第一次,避免第一次的时候没有换肤效果SkinManager.getInstance().tryInitSkin(this);}
dialog换肤也是非常简单,只需要Dialog.show()
的时候去
SkinManager.getInstance().tryInitSkin(Activity);
即可
DialogFragment换肤
这个dialog当作一个fragment用即可
和fragment注意事项相同,需要当view加载完成的时候在尝试刷新一下
@Overridepublic void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);SkinManager.getInstance().tryInitSkin(getActivity());}
最后一点:换肤只能替换View的属性,因为Factory只能拦截View,不能拦截ViewGroup
完整项目地址
原创不易,您的点赞与关注就是对我最大的支持!
本篇结束,耗时15天从框架搭建到一行代码换肤,新年前最后一篇,最后祝大家新年快乐~ 年后见
android 换肤框架搭建及使用 (3 完结篇)相关推荐
- Android-skin-support 一款用心去做的Android 换肤框架
介绍 Github地址: https://github.com/ximsfei/Android-skin-support Android-skin-support: 一款用心去做的Android 换肤 ...
- android 换肤框架
使用插件化换肤 大家可以先去网易云客户端体验一下个性换肤. 可能大家会疑惑,为什么换个皮肤,还用上了插件化,给大家解释一下. 网易云的个性换肤是可以下载皮肤然后再切换的,那么下载皮肤其实就是在一个皮肤 ...
- 插件式换肤框架搭建 - 资源加载源码分析
资源加载源码分析 1.首先我们来看一下ImageView是如何加载资源的: public ImageView(Context context, @Nullable AttributeSet attrs ...
- android的资源混淆和压缩工具,换肤框架
介绍一款可以对android资源进行压缩的工具,超好用(已经在项目中使用到) https://github.com/shwenzhang/AndResGuard Android-skin-suppor ...
- Android换肤功能实现与换肤框架QSkinLoader使用方式介绍
框架地址:https://github.com/qqliu10u/QSkinLoader 效果图 https://github.com/qqliu10u/QSkinLoader/raw/master/ ...
- Android 手写实现插件化换肤框架 兼容Android10 Android11
目录 一.收集所有需要换肤的view及相关属性 二.统一为所有Activity设置工厂(兼容Android9以上) 三.加载皮肤包资源 四.处理支持库或者自定义view的换肤 五.处理状态栏换肤 六. ...
- 红橙Darren视频笔记 换肤框架4 换肤的功能完善 内存泄漏分析
上一篇完成了换肤框架的基本搭建,这一次 我们继续补完上一次遗留的一些可以完善的部分 1.完善换肤 1.1退出后再进入应用 不会丢失上一次保存的皮肤 基本原理:将上一次切换的皮肤path保存在Share ...
- android换肤动画,Android换肤(二) — 插件式换肤
###前言 上节我们讲到了`Android-skin-support`库的应用内换肤,大家感兴趣的可以参看文章: [Android换肤(一) - 应用内换肤](http://www.demodashi ...
- Leakcanary原理解析以及换肤框架skin的原理分析
一.错误现场 java.lang.ClassCastException: androidx.appcompat.widget.TintContextWrapper cannot be cast to ...
最新文章
- solidworks画白色金属光泽_美人的共通点就是卧蚕,卧蚕真的太重要了,没有也要画出来...
- 剑指 Offer 17. 打印从1到最大的n位数
- ASP.NET Excel导入到SQL Server数据库
- numpy(5)-astype
- 首届.NET Core开源峰会
- C#中形态各异的class
- 2009-03-24 20:01 Matlab 7.0 添加BNT工具箱(转)
- java编程菜鸟入门02
- easyui treegrid 获取新添加行inserted_IDEA 2020.2 稳定版发布,带来了不少新功能...
- clobzh字符串缓冲区太小的解决方法_用4K屏玩LOL英雄联盟游戏指针太小解决方法已找到...
- html5 canvas+js贪吃蛇网页小游戏代码
- html 字体立体效果,如何利用CSS3制作3D效果文字具体实现样式
- Autodesk HSMWorks Ultimate 2019 Crack 破解版
- 怎样用计算机才能更快,如何让网速变快,详细教您怎么让电脑网速变快
- CSS单行文本溢出时显示省略号
- 大数据应用场景和大数据职业发展需要掌握的技术技能构成
- 查询活动开始时间和结束时间
- socket连接超时问题
- 如何查看电脑CPU的核心个数
- 深度学习-VGG16原理详解
热门文章
- 锤炼自己的专业学习之路
- VisualStudio:[The security key for this program currently stored on your system does not appear]解决办法
- 六步骤确保企业成功选择CRM
- 爱奇艺怎么开启主设备
- Swarm Bee配置
- Python不换行输出和不换行输出end=““不显示的问题(亲测已解决)
- 集合论编程练习 | C++ | 离散数学
- nodejs处理url中的百分号编码
- LIANJIE戳戳戳
- Java+Aspose.diagram,导出数据到Visio