填充View最终都是调用LayoutInflater
的public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
三个参数方法
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot){
··· ···
return view;
}
文档解释说明:
/**
* 只能填充特定格式的xml也就是得符合布局格式的xml,否则抛出异常InflateException。
* @param resource 要填充的资源id
* @param root 这个要和第三个参数有关系。
* 若是attachToRoot为true,那么root的意义是,从resource填充成的view对象的父控件;
* 若是attachToRoot为false,那么root的意义是,可以为resource生成的view对象的根布局提供一系LayoutParams参数的控件。
* 若root为null,也就是我们不指定父控件,那么新生产的view对象的根布局的某些参数会失效,比如Layout_width和Layout_height会失效。
* @param attachToRoot 是否附着在root上
* @return 如果attachToRoo为false,那么返回resource填充的View;
* 如果attachToRoo为true,那么返回root。
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot){
··· ···
return view;
}
常用填充View的方式有三种:
- View的静态方法,直接获取LayoutInflater进行填充
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
*
* @param context The Context object for your activity or application.
* @param resource The resource ID to inflate
* @param root A view group that will be the parent. Used to properly inflate the
* layout_* parameters.
* @see LayoutInflater
*/
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
- LayoutInflater的静态方法获取LayoutInflater,然后调用Inflater填充
/**
* Obtains the LayoutInflater from the given context.
*/
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
- 通过系统SystemService获取填充器,然后调用Inflater填充
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
注意:
View的填充方式只有两个参数,也就是调用的inflate(resource, root, root != null)
而RecyclerViewonCreateViewHolder
创建View填充时一定是inflate(resource, null)
orinflate(resource, root, false)
root为空时view的根节点部分参数无效
否则会报
真正的填充
先创建XML解析器,用于解析XML:
public View LayoutInflater.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
接下来进入到Inflate:
- 先是校验XML的合法性;
- 然后找到正确的START_TAG,获取到节点name;
- 如果是Merge标签,则递归解析Merge;如果不是Merge,则进入到createViewFromTag()中创建出root View;
- 拿到root View后,则会递归解析子View节点,最终都是通过createViewFromTag()方法来创建的;
- createViewFromTag()待会再看,解析完View后,将XML中的root View挂载到DecorView上;
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
View result = root;
...
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription() + ": No start tag found!");
}
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
return result;
}
}
真正解析和创建出View的方法createViewFromTag():
- 如果是view,则拿出属性class对应的名称,用于后面创建View实例;
- 解析属性,设置主题
- 解析BlinkLayout
- 优先使用LayoutInflater.Factory2来解析;Factory2主要是在V7包中利用委托AppCompatDelegateImpl来解析View,将老的TextView等控件,替换成AppCompatTextView,因此,我们也可以这样模仿来做换肤、统一设置字体等功能;
- 如果没有设置Factory2,那么将用Factory来进行解析;
- 如果再没有设置Factory,那么将用mPrivateFactory来创建;
- 如果没有设置mPrivateFactory,那么将通过节点名称进行反射创建;
- 反射创建时,判断了是否包含.,如果包含说明时自定义View,否则从系统默认的view来创建;
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, 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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
- Factory2
Factory2中主要是在AppCompatActivity中使用,AppCompatActivity的创建是通过AppCompatDelegate委托来实现的,在onCreate中就创建了AppCompatDelegate,并且初始化了ViewFactory; - AppCompatDelegate
功能主要是定义接口,并且创建实现类AppCompatDelegateImpl; - 在installViewFactory时,利用LayoutInflaterCompat来设置Factory,并且只接受AppCompatDelegateImpl,AppCompatDelegateImpl实现了Factory2;
- AppCompatDelegateImpl
class AppCompatDelegateImpl extends AppCompatDelegate implements MenuBuilder.Callback, LayoutInflater.Factory2
- 在AppCompatDelegateImpl中,主要通过AppCompatViewInflater来进行createView的,AppCompatViewInflater是通过反射来创建的;
- 反射创建时,区分了android.view和自定义View,但是最终都是通过反射了创建的;
看下流程
初始化AppCompatDelegate
protected void AppCompatActivity.onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
...
}
public AppCompatDelegate AppCompatActivity.getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
public static AppCompatDelegate AppCompatDelegate.create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
设置Factory
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
}
}
}
创建AppCompatViewInflater:
public View AppCompatDelegateImpl.createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName = a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if ((viewInflaterClassName == null) || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
Class viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater = (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor().newInstance();
} catch (Throwable t) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
...
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed() );
}
最终调用AppCompatViewInflater.createView
final View AppCompatViewInflater.createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
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 = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}