LayoutInflater.SetFactory()实现细节
关于这个API的使用在一些特定的功能如换肤、全局动态修改view的样式、动态修改字体等会接触到,所以还是有必要了解一下。
LayoutInflater
Activity 中都会在oncreate方法中调用setContentView方法去加载我们的布局文件 ,那么先看如何加载的:
AppCompatActivity-->
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
方法很简单先拿到一个代理对象,然后创建我们的Factory,具体创建过程由AppCompatDelegate的实现类AppCompatDelegateImpl实现:
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
public void setFactory2(Factory2 factory) {
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
到目前我们得出Activity oncreate之前会先设置LayoutInflater的Factory,并且如果已经有了Factory不会重复设置。LayoutInflaterCompat.setFactory2(layoutInflater, this)
,可以看到系统默认创建的是Factory2,this 就是当前类 AppCompatDelegateImpl实例,也就是创建view的过程在AppCompatDelegateImpl中,那怎么创建布局中的view 的,我们返回到setContentView方法,看看怎么一步步解析布局文件的:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
直接由AppCompatDelegate设置,我们看AppCompatDelegate的实现类怎么处理的:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
发现最终还是LayoutInflater的inflate处理,这个方法相信都接触过,这次跟进去看细节:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
···
View result = root;
try {
···
if (TAG_MERGE.equals(name)) {
···
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 1
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
···
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
···
return result;
}
}
可以看到注释1处调用了createViewFromTag方法,从方法名就可以看出会跟进Tag标签进行解析:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
···
try {
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
···
}
继续跟进tryCreateView:
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
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);
}
return view;
}
看到这,我们就了解了,通过layoutInflater加载布局文件,生成具体view对象的 过程是由mFactory2的 onCreateView具体操作执行,onCreateView会将我们布局中的控件替换为兼容包中的Appcompat..相关view
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpin
···
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
这就是系统替换view的过程,同样 我们也可以手动设置factory,实现view的替换、字体修改等操作,具体方法就是:
LayoutInflaterCompat.setFactory2(LayoutInflater.from(this),
object : LayoutInflater.Factory2 {
override fun onCreateView(
parent: View?,
name: String,
context: Context,
attrs: AttributeSet
): View? {
//自定义操作
return null
}
override fun onCreateView(
name: String,
context: Context,
attrs: AttributeSet
): View? {
return null
}
})
super.onCreate(savedInstanceState)
注意一定要在super.onCreate(savedInstanceState)
之前调用,这样在mFactory2.onCreateView的时候就会回调到我们自己的onCreateView方法中
一些细节
一:如果我们什么都不做直接return null,像上面写的这样,会怎样,会不会崩溃?其实不会,我们回到之前看到的tryCreateView:
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
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;
}
//1
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
发现如果view返回null,会执行到注释1处执行mPrivateFactory的onCreateView方法,这个又是什么,这个具体是在Activity类中设置的:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
···
mWindow.getLayoutInflater().setPrivateFactory(this);
···
在activity的attach方法中setPrivateFactory(activity)
,也就是
mPrivateFactory.onCreateView又会调用到 Activity类中,好,看看做了什么:
public View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
return mFragments.onCreateView(parent, name, context, attrs);
}
public View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
return null;
}
不考虑布局文件中含有fragment标签的话,会直接return null,也就是Activity中默认空实现,这也说明我们可以实现该方法做到修改字体、更改样式、替换View的操作,既然默认return null,那默认怎么生成view对象?那就是在调用tryCreateView方法中如果view为null,系统会默认处理。
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
二:目前都是生成一个view,怎么循环解析布局里面的各个标签的呢?看刚开始我们在createViewFromTag之后,做了rInflateChildren(parser, temp, attrs, true);
跟进去:
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
···
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
看到其实是递归的解析我们的布局。
到此,分析了LayoutInflater.SetFactory()的操作,和怎么自定义去写布局。