简书链接:android动态换肤使用本地资源原理分析
文章字数:760,阅读全文大约需要3分钟
大致原理:
在application里面注册所有activity回调这样可以实现很少的改动侵入性
2. 给LayoutFactory设置自己的factory2
,工厂2 使activity在setContentView
调用inflate的时候触发自己的factory的创建view方法.,为什么可以呢?因为LayoutFactory.from(this)
当前activity填充的时候调用多次实际上还是同样的对象,所以可以这么简单的进行了hook 不需要进行更高级的hook from返回值类似的方法操作。
- 创建的时候根据属性和节点判断是否需要更换皮肤,比如#开头引用的可以忽略,对于属性 ```?``修饰或者是@引用的进行记录,在view构造方法创建完毕之后设置一次皮肤就完成了更改. 先通过原来apk的id转换为资源名称然后 用这个资源名称在自己构建的Resource里面进行加载如果能加载就用自己的,否则还是用系统的.其实如果没加载到干脆不操作也是可以的.
- 比如对背景颜色进行换肤,实际上一个view的setBackground方法也许会调用2次,因为第一次是构造的时候new ImageView(context,attrs)的时候进行了一次初始化,构造完成之后更换加载默认皮肤又进行了一次替换.
大致经典代码:
创建自己的Resource
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| AssetManager assetManager = AssetManager.class.newInstance(); // 添加资源进入资源管理器 Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String .class); addAssetPath.setAccessible(true); addAssetPath.invoke(assetManager, path);
Resources resources = application.getResources(); Resources skinResource = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration()); PackageManager mPm = application.getPackageManager(); PackageInfo info = mPm.getPackageArchiveInfo(path, PackageManager .GET_ACTIVITIES); String packageName = info.packageName; } catch (Exception e) { e.printStackTrace(); }
|
上面的方法是拿到皮肤apk 然后自己构造一个Resources.并调用资源管理器的addAssetPath添加进去,避免不合法,但是此时id是不相同的,
view的属性节点操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| /** * * @param view * @param attrs */ public void parseViewInfo(View view, AttributeSet attrs) { List<SkinPair> skinPairs = new ArrayList<>(); for (int i = 0; i < attrs.getAttributeCount(); i++) {
String attributeName = attrs.getAttributeName(i); if (mAttributes.contains(attributeName)) { String attributeValue = attrs.getAttributeValue(i); if (attributeValue.startsWith("#")) { continue; } //资源id int resId; if (attributeValue.startsWith("?")) { //得到属性id 解析成具体值id int attrId = Integer.parseInt(attributeValue.substring(1)); resId = SkinThemeUtils.getResIdByAttr(view.getContext(), new int[]{attrId})[0]; } else { // @694886526 resId = Integer.parseInt(attributeValue.substring(1)); } if (resId != 0) { //对于匹配的成功找到的进行添加 方便更换 } } }
}
|
换肤的操作
换肤的时候只需要查找是否匹配,成功通过资源名找到资源id才进行替换
1 2 3 4 5 6 7 8 9 10 11
| public int getIdentifier(int resId) { if (isDefaultSkin) { return resId; }
//R.drawable.ic_launcher String resName = mAppResources.getResourceEntryName(resId);//ic_launcher String resType = mAppResources.getResourceTypeName(resId);//如 drawable color int skinId = mSkinResources.getIdentifier(resName, resType, mSkinPkgName); return skinId; }
|
1 2 3 4 5 6 7 8 9 10
| public int getColor(int resId) { if (isDefaultSkin) { return mAppResources.getColor(resId); } int skinId = getIdentifier(resId); if (skinId == 0) { return mAppResources.getColor(resId); } return mSkinResources.getColor(skinId); }
|
上面的方法拿颜色值是先判断是否启用皮肤如果没有启用皮肤那么根据getIdentifier(resid)
方法先传递原始属性id得获取资源名称,然后把资源名称和类型传递进去再取调用自己创建的皮肤Resources
类
1
| mSkinResources.getIdentifier(resName, resType, mSkinPkgName);
|
如果能拿到自然就从自己的皮肤资源里面获取颜色值了,那么对于属性替换又是如何操作呢?
1 2 3 4 5 6 7 8 9
| public static int[] getResIdFromAttrs(Context context, int[] attrs) { int[] resIds = new int[attrs.length]; TypedArray typedArray = context.obtainStyledAttributes(attrs); for (int i = 0; i < typedArray.length(); i++) { resIds[i] = typedArray.getResourceId(i, 0); } typedArray.recycle(); return resIds; }
|