博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
2.3 Android 换肤原理
阅读量:6805 次
发布时间:2019-06-26

本文共 11311 字,大约阅读时间需要 37 分钟。

Android 换肤原理

  • 制作皮肤包,皮肤包相当于一个apk,不过只包含了资源文件
  • 获取到皮肤包的Resource对象
  • 标记需要换肤的View
  • 切换时刷新页面

换肤用的Api

1.通过的Resource获取皮肤包中资源(一般是图片,颜色)的id值
public class Resources {    /********部分代码省略*******/    /**     * 通过给的资源名称返回一个资源的标识id。     * @param name 描述资源的名称     * @param defType 资源的类型     * @param defPackage 包名     *      * @return 返回资源id,0标识未找到该资源     */    public int getIdentifier(String name, String defType, String defPackage) {        if (name == null) {            throw new NullPointerException("name is null");        }        try {            return Integer.parseInt(name);        } catch (Exception e) {            // Ignore        }        return mAssets.getResourceIdentifier(name, defType, defPackage);    }}复制代码
2. AssetManage用于构造获取皮肤包的Resource对象

创建一个包含皮肤包packageName的AssetManage对象实例

AssetManager assetManager = AssetManager.class.newInstance();/** * apk路径 */String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk";AssetManager assetManager = null;try {    AssetManager assetManager = AssetManager.class.newInstance();    AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath);} catch (Throwable th) {    th.printStackTrace();}复制代码
3. 创建获取换肤包的Resource实例了:
public Resources getSkinResources(Context context){    /**     * 插件apk路径     */    String apkPath = Environment.getExternalStorageDirectory()+"/skin.apk";    AssetManager assetManager = null;    try {        AssetManager assetManager = AssetManager.class.newInstance();        AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath);    } catch (Throwable th) {        th.printStackTrace();    }    return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());}复制代码
4. 使用皮肤包中的资源,对app进行换肤
@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    ImageView imageView = (ImageView) findViewById(R.id.imageView);    TextView textView = (TextView) findViewById(R.id.text);    /**     * 插件资源对象     */    Resources resources = getSkinResources(this);    /**     * 获取图片资源     */    Drawable drawable = resources.getDrawable(resources.getIdentifier("night_icon", "drawable","com.tzx.skin"));    /**     * 获取Color资源     */    int color = resources.getColor(resources.getIdentifier("night_color","color","com.tzx.skin"));    imageView.setImageDrawable(drawable);    textView.setText(text);}复制代码

Android-Skin-Loader 换肤原理

1.load皮肤包
/**  * Load resources from apk in asyc task  * @param skinPackagePath path of skin apk  * @param callback callback to notify user  */public void load(String skinPackagePath, final ILoaderListener callback) {     new AsyncTask
() { protected void onPreExecute() { if (callback != null) { callback.onStart(); } }; @Override protected Resources doInBackground(String... params) { try { if (params.length == 1) { String skinPkgPath = params[0]; File file = new File(skinPkgPath); if(file == null || !file.exists()){ return null; } PackageManager mPm = context.getPackageManager(); //检索程序外的一个安装包文件 PackageInfo mInfo = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES); //获取安装包报名 skinPackageName = mInfo.packageName; //构建换肤的AssetManager实例 AssetManager assetManager = AssetManager.class.newInstance(); Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class); addAssetPath.invoke(assetManager, skinPkgPath); //构建换肤的Resources实例 Resources superRes = context.getResources(); Resources skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration()); //存储当前皮肤路径 SkinConfig.saveSkinPath(context, skinPkgPath); skinPath = skinPkgPath; isDefaultSkin = false; return skinResource; } return null; } catch (Exception e) { e.printStackTrace(); return null; } }; protected void onPostExecute(Resources result) { mResources = result; if (mResources != null) { if (callback != null) callback.onSuccess(); //更新多有可换肤的界面 notifySkinUpdate(); }else{ isDefaultSkin = true; if (callback != null) callback.onFailed(); } }; }.execute(skinPackagePath);}复制代码
2. 换肤页面的基类,设置布局文件加载器LayoutInflate.Fatory

用于在加载布局文件创建View的时候,统计需要换肤的View对象

public class BaseFragmentActivity extends FragmentActivity implements ISkinUpdate, IDynamicNewView{        /***部分代码省略****/        //自定义LayoutInflater.Factory    private SkinInflaterFactory mSkinInflaterFactory;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);            try {            //设置LayoutInflater的mFactorySet为true,表示还未设置mFactory,否则会抛出异常。            Field field = LayoutInflater.class.getDeclaredField("mFactorySet");            field.setAccessible(true);            field.setBoolean(getLayoutInflater(), false);            //设置LayoutInflater的MFactory            mSkinInflaterFactory = new SkinInflaterFactory();            getLayoutInflater().setFactory(mSkinInflaterFactory);        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (IllegalArgumentException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }             }    @Override    protected void onResume() {        super.onResume();        //注册皮肤管理对象        SkinManager.getInstance().attach(this);    }        @Override    protected void onDestroy() {        super.onDestroy();        //反注册皮肤管理对象        SkinManager.getInstance().detach(this);    }   }复制代码
3. SkinInflaterFactory 自定义布局文件加载器

在某个界面初始化在加载布局文件中的View的时候,通过自定义的布局文件解析器,创建View。

这里有一个判断,每次创建View的时候,会判断改View的skin:enable属性,如果为false,则不支持换肤,直接返回null,将View的创建的流程交给Activity自己创建。只有shin:enable属性为true的时候,才会自己创建View对象,并且在创建的View的同时,解析支持换肤的属性

public class SkinInflaterFactory implements Factory {    public View onCreateView(String name, Context context, AttributeSet attrs) {        //读取View的skin:enable属性,false为不需要换肤        // if this is NOT enable to be skined , simplly skip it         boolean isSkinEnable = attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false);        if (!isSkinEnable){                return null;        }        //创建View        View view = createView(context, name, attrs);        if (view == null){            return null;        }        //如果View创建成功,对View进行换肤        parseSkinAttr(context, attrs, view);        return view;    }    //创建View,类比可以查看LayoutInflater的createViewFromTag方法    private View createView(Context context, String name, AttributeSet attrs) {        View view = null;        try {            if (-1 == name.indexOf('.')){                if ("View".equals(name)) {                    view = LayoutInflater.from(context).createView(name, "android.view.", attrs);                }                 if (view == null) {                    view = LayoutInflater.from(context).createView(name, "android.widget.", attrs);                }                 if (view == null) {                    view = LayoutInflater.from(context).createView(name, "android.webkit.", attrs);                }             }else {                view = LayoutInflater.from(context).createView(name, null, attrs);            }            L.i("about to create " + name);        } catch (Exception e) {             L.e("error while create 【" + name + "】 : " + e.getMessage());            view = null;        }        return view;    }}复制代码
4. 创建了View之后,解析布局文件中View定义的换肤属性

解析View的属性,在某些属性(比如background,)支持换肤的时候,会将解析到的属性保存在一个SkinItem的对象中,每解析一个View都会生成一个SkinItem对象,然后保存在一个ArrayList 集合中,在点击切换按钮的时候,会遍历该List,替换资源。

public class SkinInflaterFactory implements Factory {    //存储当前Activity中的需要换肤的View    private List
mSkinItems = new ArrayList
(); private void parseSkinAttr(Context context, AttributeSet attrs, View view) { //当前View的所有属性标签 List
viewAttrs = new ArrayList
(); for (int i = 0; i < attrs.getAttributeCount(); i++){ String attrName = attrs.getAttributeName(i); String attrValue = attrs.getAttributeValue(i); if(!AttrFactory.isSupportedAttr(attrName)){ continue; } //过滤view属性标签中属性的value的值为引用类型 if(attrValue.startsWith("@")){ try { int id = Integer.parseInt(attrValue.substring(1)); String entryName = context.getResources().getResourceEntryName(id); String typeName = context.getResources().getResourceTypeName(id); //构造SkinAttr实例,attrname,id,entryName,typeName //属性的名称(background)、属性的id值(int类型),属性的id值(@+id,string类型),属性的值类型(color) SkinAttr mSkinAttr = AttrFactory.get(attrName, id, entryName, typeName); if (mSkinAttr != null) { viewAttrs.add(mSkinAttr); } } catch (NumberFormatException e) { e.printStackTrace(); } catch (NotFoundException e) { e.printStackTrace(); } } } //如果当前View需要换肤,那么添加在mSkinItems中 if(!ListUtils.isEmpty(viewAttrs)){ SkinItem skinItem = new SkinItem(); skinItem.view = view; skinItem.attrs = viewAttrs; mSkinItems.add(skinItem); //是否是使用外部皮肤进行换肤 if(SkinManager.getInstance().isExternalSkin()){ skinItem.apply(); } } }}复制代码
5. 在进行换肤的时候,替换资源

通过当前的资源id,找到对应的资源name。再从皮肤包中找到该资源name所对应的资源id。

public class SkinManager implements ISkinLoader{    /***部分代码省略****/    public int getColor(int resId){        int originColor = context.getResources().getColor(resId);        //是否没有下载皮肤或者当前使用默认皮肤        if(mResources == null || isDefaultSkin){            return originColor;        }        //根据resId值获取对应的xml的的@+id的String类型的值        String resName = context.getResources().getResourceEntryName(resId);        //更具resName在皮肤包的mResources中获取对应的resId        int trueResId = mResources.getIdentifier(resName, "color", skinPackageName);        int trueColor = 0;        try{            //根据resId获取对应的资源value            trueColor = mResources.getColor(trueResId);        }catch(NotFoundException e){            e.printStackTrace();            trueColor = originColor;        }                return trueColor;    }    public Drawable getDrawable(int resId){...}}复制代码

这样整个换肤的流程就走完了

转载于:https://juejin.im/post/5b88b602e51d4538b20493a2

你可能感兴趣的文章
BI技术
查看>>
检查hdfs块的块-fsck
查看>>
Asp程序的IIS发布
查看>>
设计模式4-代理模式
查看>>
php7扩展开发[8]类方法之间的调用
查看>>
通过C语言HelloWord程序对计算系统理解
查看>>
vue之better-scroll的封装,包含下拉刷新,上拉加载功能及UI(核心为借鉴,我仅仅是给轮子套上了外胎...)...
查看>>
HTML基础-------最初概念以及相关语法
查看>>
如何理解代理?
查看>>
python基础:冒泡和选择排序算法实现
查看>>
python基础一 day13 复习
查看>>
appium===安卓SDK下载很慢的解决办法
查看>>
java.面向对象特征
查看>>
poj2月下旬题解
查看>>
json格式返回到cmd中crul中文乱码问题
查看>>
统计学习概念
查看>>
Oracle学习笔记--第3章 使用sql*plus工具
查看>>
各驱动器和URL
查看>>
javascript生成二维码
查看>>
iOS开发之 -- 判断tableview/scrollview的滑动方法,及导航栏渐变的实现代码
查看>>