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
@Override
public 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
@Override
protected 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并对其做相应的修改操作。
来源:CSDN
作者:曾家小浩
链接:https://blog.csdn.net/qq_33833831/article/details/77449049