Android应用开发Android:换肤技术讲解
白羽 2019-05-20 来源 :网络 阅读 933 评论 0

摘要:本文将带你了解Android应用开发Android:换肤技术讲解,希望本文对大家学Android有所帮助。

    本文将带你了解Android应用开发Android:换肤技术讲解,希望本文对大家学Android有所帮助。


Android应用开发Android:换肤技术讲解


主题,是许多APP必备的一个功能,用户可以根据自己的喜好,来切换具有个性的主题,同时能让我们的APP更具把玩性。这篇博文就来聊聊皮肤切换的原理,
 
    这里为了便于理解,在换肤的时候,只是简单切换背景图片,文件颜色和组件背景色
    这篇博文将用到一下知识点:
   
    classLoader:实例化控件 PackageManager:拿到插件的包信息 反射:拿到插件的resource LayoutInflaterFactory:解析xml
    一、思路
      首先通过LayoutInflaterCompat的setFactory方法设置自定义的LayoutInflaterFactory,并实现onCreateView,我们可以在该方法中解析xml的每一个节点(即view   ),先通过组件名创建对应的view ,再遍历每一个view的 attrs属性和值,并以map保存,以便后续调用(即皮肤资源的切换)。
    我们知道,在Android中是通过resource来获取资源的,若能获取插件的resource对象,那么就可以获取其图片等资源,到达换肤的目的,说得简单点,换肤就是换resource和packName。
    在拿到插件的resource之后,就可以通过resource Id 来给每一个view设置其属性(如background)
    当然,道理想必大家都懂,show me your code ~
   
    二、偷梁换柱,换掉应用的LayoutInflaterFactory
    我们需要持切换皮肤的组件,因此创建SkinFactory类实现LayoutInflaterFactory接口,并实现该接口中的方法onCreateView
   

    /**
        * Hook you can supply that is   called when inflating from a LayoutInflater.
        * You can use this to customize   the tag names available in your XML
        * layout files.
        *
        * @param parent The parent that   the created view will be placed
        * in; note that this   may be null.
        * @param name Tag name to be   inflated.
        * @param context The context the   view is being created in.
        * @param attrs Inflation   attributes as specified in XML file.
        *
        * @return View Newly created   view. Return null for the default
        *         behavior.
        */
       View onCreateView(View parent,   String name, Context context, AttributeSet attrs);
    显然这是一个hook,   执行LayoutInflater.inflate()的时候调用,如上所述,我们可以通过该方法获取每一个节点的属性和值(即资源id),资源类型(drawable   、color 等)。先简单介绍这四个参数:
   
    parent:即当前节点的父类节点,可能为null name :节点名,列如 TextView context :该执行过程的上下文   attrs:该节点的属性集合,例如 background属性
      那么,我们怎么通过节点来创建对应的组件对象呢?我们都知道在android.widget包下的Button在布局文件中的节点名只有Button,并不是完整的包路径,例如
   
     以及android.view包下的SurfaceView等等。
   

   
    想必读者明白列举以上的用意了,对,我们需要先对获取到的节点名字进行处理,判断获取到的节点名是系统组件,还是自定义组件,从而构建完整的class   name 。如下代码
   


    private static final String[] preFixList = {
              "android.widget.",
              "android.view.",
              "android.webkit."
    };  //这些都是系统组件
    @Override
    public View onCreateView(View parent, String name, Context context,   AttributeSet attrs) {
        View view = null;
        if (name.indexOf(".")   == -1) {
            //系统控件
            for (String prix :   preFixList) {
                view =   createView(context, attrs, prix + name);
                if (null != view) {
                    break;
                }
            }
        } else {
            //自定义控件
            view = createView(context,   attrs, name);
        }
        if (null != view) {
            parseSkinView(view, context,   attrs);
        }
        return view;
    }
    这里需要我们返回一个view,即该组件对应的view,既然能拿到组件对应的class   name,那就好办,直接通过classloader去load一个class即可
   

    //创建一个view
    private View createView(Context context, AttributeSet attrs, String name)   {
        try {
            //实例化一个控件
            Class clarr =   context.getClassLoader().loadClass(name);
            Constructor constructor =
                      clarr.getConstructor(new Class[]{Context.class,   AttributeSet.class});
              constructor.setAccessible(true);
            return   constructor.newInstance(context, attrs);
        } catch (ClassNotFoundException   e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e)   {
            e.printStackTrace();
        } catch (IllegalAccessException   e) {
            e.printStackTrace();
        } catch (InstantiationException   e) {
            e.printStackTrace();
        } catch   (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
    在拿到该控件后,需要遍历其需要替换值的属性,例如background,存放在list集合中。
   
 
    //找到需要换肤的控件
    private void parseSkinView(View view, Context context, AttributeSet attrs)   {
        List  attrList = new ArrayList<>();
        for (int i = 0; i <   attrs.getAttributeCount(); i++) {
            //拿到属性名
            String attrName =   attrs.getAttributeName(i);
            String attrValue =   attrs.getAttributeValue(i);
            int id =-1;
            String entryName   ="";
     
            String typeName   ="";
     
            SkinInterface skinInterface =   null ;
     
            switch (attrName) {
                case   "background"://需要进行换肤
                    id =   Integer.parseInt(attrValue.substring(1));
                    entryName =   context.getResources().getResourceEntryName(id);
                    typeName =   context.getResources().getResourceTypeName(id);
                    skinInterface = new   BackgroundSkin(attrName,id,entryName,typeName);
                    break;
                case   "textColor":
                    id =   Integer.parseInt(attrValue.substring(1));
                    entryName =   context.getResources().getResourceEntryName(id);
                    typeName =   context.getResources().getResourceTypeName(id);
                    skinInterface = new   TextSkin(attrName,id,entryName,typeName);
                    break;
                default:
                    break;
            }
            if(null !=   skinInterface){
                  attrList.add(skinInterface);
            }
     
        }
        SkinItem skinItem = new   SkinItem(attrList,view);
        map.put(view,skinItem);
        //在这里进行应用,判断是皮肤资源还是本地资源
        skinItem.apply();
    }

    为了方便属性的替换,这里用SkinItem对象来持有view和view对应的属性集合list。
   

    class SkinItem {
          public   SkinItem(ListattrList, View view) {
              this.attrList =   attrList;
              this.view = view;
          }
     
          public   ListattrList;
          public View view;
          //更新组件资源,调用skinInterface   的实现类body{ }    

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道!

本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程