android插件化主题方案 (上--LayoutInflateFactory的使用)

来源:互联网 发布:淘宝联盟默认推广位 编辑:程序博客网 时间:2024/05/17 07:36

android插件化主题方案 (上–LayoutInflateFactory的使用)

标签(空格分隔): android 主题 皮肤 插件化


“做成网易音乐那样的!”
这次产品经理提出的需求就是像网易音乐那样可以更换主题皮肤,当然皮肤切换很多app都有,产品经理也明确表示需要后台有皮肤主题管理能力,所以这次的功能免不了要做成外挂式,不能简单的在资源文件编写多套value的方式实现。
那我们就来一步步的实现吧。


首先,我们先不管插件化的方式,先来看看如何在不更改已编写xml的情况下,快速替换view的资源。

setContentView()做了些什么?
我们知道默认onCreate()执行时,马上会调用setContentView(int id)方法,将layout资源与activity进行绑定,我们看看setContentView做了些什么。

public void setContentView(@LayoutRes int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();}

这是Activity类中的setContentView()的方法,我们继续往下看。

@Override  public void setContentView(int layoutResID) {     if (mContentParent == null) {         installDecor();     } else {         mContentParent.removeAllViews();     }     mLayoutInflater.inflate(layoutResID, mContentParent);     final Callback cb = getCallback();     if (cb != null && !isDestroyed()) {         cb.onContentChanged();     } }  

在这里我们看到了熟悉的充气类,我们View的创建离不开Inflate,那么它在创建View的时候做了什么呢?

   public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {   ....       final View temp = createViewFromTag(root, name, inflaterContext, attrs);       ...}

我们找到这个创建view的方法

 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,        boolean ignoreThemeAttr) {        ...          View view;        if (mFactory2 != null) {            view = mFactory2.onCreateView(parent, name, context, attrs);        } else if (mFactory != null) {            view = mFactory.onCreateView(name, context, attrs);        } else {            view = null;        }        if (view == null && mPrivateFactory != null) {            view = mPrivateFactory.onCreateView(parent, name, context, attrs);        }        ...        }

最终我们的view是通过factory进行创建的,
而这个factory就是我们这次需要重点介绍的工具。

   /** * Used with {@code LayoutInflaterCompat.setFactory()}. Offers the same API as * {@code LayoutInflater.Factory2}. */public interface LayoutInflaterFactory {    /**     * 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; <em>note that this may be null</em>.     * @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.     */    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);}

LayoutInflateFactory是一个接口,其只有一个方法,我们重点关注注释,简单理解为

提供给你一个钩子(可以理解为监听),用以在inflate的时候用名字作为比对,生成对应的View对象进行返回。

so,整个inflate的过程大致就是,xmlParser对xml进行解析,然后传递至Factory根据名字生产相对应的View类,然后返回上层进行添加,好了,下面,我们就可以通过实现这个接口,在onCreate()中对生成的view设置各种属性,这样,我们就能在xml中书写固定的资源情况下,动态对view的属性进行更改。

自定义Factory

 @Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs) {    boolean isSkinEnable = attrs.getAttributeBooleanValue(SkinConfig.NAMESPACE, SkinConfig.ATTR_SKIN_ENABLE, false);    AppCompatDelegate delegate = mAppCompatActivity.getDelegate();    View view = delegate.createView(parent, name, context, attrs);    if (view instanceof TextView && SkinConfig.isCanChangeFont()) {        TextViewRepository.add(mAppCompatActivity, (TextView) view);    }    if (isSkinEnable || SkinConfig.isGlobalSkinApply()) {        if (view == null) {            view = ViewProducer.createViewFromTag(context, name, attrs);        }        if (view == null) {            return null;        }        parseSkinAttr(context, attrs, view);    }    return view;}

这里的isSkinEnable我们用来在xml标记该view需要更换皮肤(自定义命名空间),下面判断TextView的部分用来处理更换字体,暂且不看,其余的创建View的方法于源码相同,创建View之后,我们用parseSkinAttr()方法进行view的处理,当然之前我们需要判断该view是否需要进行切换。
在这个方法中,我们就可以对这个view进行各种处理,更改背景,更改imageView的src等等。
waring
这个factory只用于在界面初始化(比如打开一个activity)的时候进行主题的更改,在界面被绘制完毕后,我们需要更改这个界面所有view的属性的话,得调用其他方法,所以为了标记那些view需要更改,我们在这个方法中可以定义一个集合,在初始化的时候进行保存,以方便后续处理。

  SkinItem skinItem = new SkinItem();        skinItem.view = view;        skinItem.attrs = viewAttrs;        mSkinItemMap.put(skinItem.view, skinItem);

skinItem就是需要更改的view及其属性的封装对象。
替换activity的factory为自定义的factory

  @Overrideprotected void onCreate(Bundle savedInstanceState) {    mSkinInflaterFactory = new SkinInflaterFactory();    mSkinInflaterFactory.setAppCompatActivity(this);    LayoutInflaterCompat.setFactory(getLayoutInflater(), mSkinInflaterFactory);    super.onCreate(savedInstanceState);    changeStatusColor();}

这里我建议书写一个baseActivity,在super.onCreate之前将activity与Factory进行绑定。

这样在每一个activity启动之后,创建view时,我们都可以控制到该view的属性,而我们将这些view保存之后,我们同样可以在activity已经创建完毕的条件下,准确找到需要更改的view并对其做相应的修改操作。

阅读全文
2 0
原创粉丝点击