android 换肤(2)——插件式无缝换肤(解析鸿洋大神的换肤流程)

上一篇我说到tag式换肤的流程。

小结:
你只需要在每一个需要换肤的activity中注册SkinManager就可以换肤了,并且在需要换肤的资源中xml或者代码中都要设置tag,且这个tag是严格按格式来的。

这就是非侵入是换肤:对程序的影响最小,但是很麻烦,你要为每一个需要换肤的资源配置tag。

所以,侵入式换肤解决的就是这个问题。你在也不用注册SkinManager了,也不用在每个属性中都加入格式繁琐的tag了。

对于侵入式来说,你只需要规范的命名你的资源id就ok了。

接下来我们看看侵入式的换肤。

侵入式换肤的SkinManager没有了注册和反注册方法,其他的差不多。这里就不贴出来了,如果想要了解的,在(1)中已经给出了鸿洋大神的项目地址,自己可以去看。

侵入式换肤调用的SkinAttrSupport中的getSkin方法并不再是“getSkinTags(String tag)”了。而是“getSkinAttrs(AttributeSet attrs, Context context)”

可以看到,我们只需要传入view的资源集AttributeSet和上下文就ok。已经脱离了tag。

该方法我在(1)中也贴出来了,这里就不再贴出来了。

侵入式换肤最主要的方法在于baseSkinactivity中:
这是鸿洋大神的BaseSkinActivity。侵入了onCreateView方法,使用了视图兼容工厂。
这里面如何侵入的,我只能看看,解释的可能也有出入。

**
 * 如果需要换肤的activity就要继承该activity
 * 该activity实现了ISkinListener接口,可以为换肤提供一个标识
 * 实现LayoutInflaterFactory是换肤的主要方法
 */
public class BaseSkinActivity extends AppCompatActivity implements ISkinListener, LayoutInflaterFactory {
    //构造函数
    private static final Class<?>[] sConstructorSignature = new Class[]{Context.class, AttributeSet.class};
    //构造函数的集合
    private static final Map<String, Constructor<? extends View>> sConstructorMap = new ArrayMap<>();
    //构造函数参数,长度为2
    private final Object[] mConstructorArgs = new Object[2];
    //供反射使用的方法实例,创建view的方法
    private Method sCreateViewMethod;
    //该方法参照onCreateView,class中传入的参数是和onCreateView的参数一样。
    private static final Class<?>[] sCreateViewSignature = new Class[]{View.class, String.class, Context.class, AttributeSet.class};

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

        //这一段代码在通过反射得到该activity创建的时候的view实例,app兼容委托实例,即当前activity的实例
        AppCompatDelegate delegate = getDelegate();
        View view = null;
        try {
            if (sCreateViewMethod == null)
                //反射createView方法,并传入相应的参数
                sCreateViewMethod = delegate.getClass().getMethod("createView", sCreateViewSignature);
            //传入该方法所在类的类实例,以及参数
            view = (View) sCreateViewMethod.invoke(delegate, parent, name, context, attrs);
        } catch (InvocationTargetException e) {
            //目标调用异常
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            //非法访问异常
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            //没有这样的方法异常
            e.printStackTrace();
        }
        //通过传入该activity的属性和上下文,得到这个activity所持有的所有属性实例
        List<SkinAttr> skinAttrList = SkinAttrSupport.getSkinAttrs(attrs, context);
        //如果这个activity里面没有使用资源则直接返回view
        if (skinAttrList.isEmpty()) {
            return view;
        }
        //如果通过反射没有得到view则手动创建一个
        if (view == null) {
            //将该activity的参数传入
            view = createViewFromTag(context, name, attrs);
        }
        //给当前activity注入皮肤
        injectSkin(view, skinAttrList);
        return view;
    }

    //通过标签来创建一个view
    private View createViewFromTag(Context context, String name, AttributeSet attrs) {
        if (name.equals("view"))
            //根据属性名称来查找并返回一个指定的属性值
            name = attrs.getAttributeValue(null, "class");
        try {
            mConstructorArgs[0] = context;
            mConstructorArgs[1] = attrs;
            if (-1 == name.indexOf('.'))//如果属性值中不包含.则手动创建一个前缀;若包含传null
                return createView(context, name, "android.widget.");
            else
                return createView(context, name, null);
        } catch (Exception e) {
            return null;
        } finally {
            // 最后将上下文清空,不然会造成内存泄漏
            mConstructorArgs[0] = null;
            mConstructorArgs[1] = null;
        }
    }

    //创建视图
    private View createView(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        try {
            if (constructor == null) {
                //将缓存中的view对象拿出来
                Class<? extends View> clazz = context.getClassLoader().loadClass(!TextUtils.isEmpty(prefix) ? (prefix + name) : name).asSubclass(View.class);
                //获得该view构造函数集合
                constructor = clazz.getConstructor(sConstructorSignature);
                //将构造函数传入map集合
                sConstructorMap.put(name, constructor);
            }
            //使用反射机制可以打破封装性,导致了java对象的属性不安全。设置可访问性
            constructor.setAccessible(true);
            //传入构造方法作为参数,获得对象并返回
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            return null;
        }
    }

    //传入当前activity的view和所有的属性资源,注入皮肤
    private void injectSkin(View view, List<SkinAttr> skinAttrList) {
        if (skinAttrList.size() > 0) {
            //得到activity中保存的皮肤view集合,如果是第一次来获取
            List<SkinView> skinViews = SkinManager.getInstance().getSkinViews(this);
            if (skinViews == null)
                skinViews = new ArrayList<>();
            //把所有的属性资源和当前activity传入skinView中,并添加到skinview的集合中
            skinViews.add(new SkinView(view, skinAttrList));
            SkinManager.getInstance().addSkinView(this, skinViews);
            //如果可以执行换肤,则执行;
            if (SkinManager.getInstance().needChangeSkin()) {
                //换肤
                SkinManager.getInstance().apply(this);
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //将该类添加进视图兼容工厂
        LayoutInflaterCompat.setFactory(LayoutInflater.from(this), this);
        super.onCreate(savedInstanceState);
        //在activity创建的时候添加换肤的监听
        SkinManager.getInstance().addChangedListener(this);
    }

    /**
     * 当程序要求app修改皮肤的时候就会调用这个方法
     * 该方法里面写实时的修改皮肤的方法
     */
    @Override
    public void onSkinChanged() {
        //因为在onCreateView方法中已经将参数声明了,所以这里直接调用
        SkinManager.getInstance().apply(this);
    }

    /**
     * 当activity被finish的时候我们从换肤集合中移除这个activity,下一次换肤就不会对该activity产生作用了
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        SkinManager.getInstance().removeChangedListener(this);
    }
}

经过我苦逼的注释后,我发现大神的代码果真非同凡响。侵入式方法虽然减少了很多工作量,但是带来了一个很重要的问题。
如果我们在侵入onCreateView方法的时候出错了,,,,,那我们就很那解决问题了。

不要认为这个方法不会出bug,鸿洋大神在微博上都说过还要测试bug。。。

而且侵入式方法的代码并不美观,可读性并不高。如果让我选择,我还是宁愿麻烦一点,选择tag换肤,这样代码清晰,逻辑易懂,修改也相对容易了,

当然,可能以后会有更好的方法来代替tag换肤。
侵入式方法虽好可使用需谨慎。
不过这也是我们值得向大神学习的地方,一个新思路成就一种轮子。不管这个方法可取不可取,这种思想是非常值得学习的!

demo下载

  • 0
    点赞
  • 3
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值