目录
3.rInflate方法/rInflateChildren方法
一、介绍
/**
* Instantiates a layout XML file into its corresponding {@link android.view.View}
* objects. It is never used directly. Instead, use
* {@link android.app.Activity#getLayoutInflater()} or
* {@link Context#getSystemService} to retrieve a standard LayoutInflater instance
* that is already hooked up to the current context and correctly configured
* for the device you are running on.
* 将布局XML文件实例化为其相应的View对象。它从不直接使用。而是使用android.app.Activity。
* getLayoutInflationr()或Context。getSystemService以检索已连接到当前上下文并为正在运行的
* 设备正确配置的标准LayoutInflationer实例。
*
* <p>
* To create a new LayoutInflater with an additional {@link Factory} for your
* own views, you can use {@link #cloneInContext} to clone an existing
* ViewFactory, and then call {@link #setFactory} on it to include your
* Factory.
* 使用其他LayoutInflationer创建新LayoutInflationr。对于您自己的视图,可以使用cloneContext克
* 隆现有的ViewFactory,然后对其调用setFactory以包含Factory。
*
* <p>
* For performance reasons, view inflation relies heavily on pre-processing of
* XML files that is done at build time. Therefore, it is not currently possible
* to use LayoutInflater with an XmlPullParser over a plain XML file at runtime;
* it only works with an XmlPullParser returned from a compiled resource
* (R.<em>something</em> file.)
* 出于性能原因,视图膨胀严重依赖于在构建时完成的XML文件的预处理。因此,当前不可能在运行时在纯
* XML文件上使用LayoutInflationer和XmlPullParser;它只适用于从编译资源(R.something文件)返
* 回的XmlPullParser
*/
@SystemService(Context.LAYOUT_INFLATER_SERVICE)
public abstract class LayoutInflater {
.......
}
以上是源码中,官方对LayoutInflater的介绍。总结就是,用于实例layout布局文件中的View。
二、使用
正常的使用方式,返回出View。
View v = LayoutInflater.from(mContext).inflate(R.layout.xxx, parent, false);
看看inflate的几个重载函数。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
XmlPullParser这里就不贴了,其实大致内容跟inflate要走的流程几乎一样。参数存在几种状况
- root=null attach=false
- root=null attach=true
- root != null attach=false
- root != null attach=true
三、源码分析
1.inflate方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//尝试预编译,一般情况mUseCompiledView为false,会跳过预编译的代码段,返回null
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//获取解析xml的对象
XmlResourceParser parser = res.getLayout(resource);
try {
//下一步骤
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;//将父容器赋值给result,最后返回出去的是result
try {
advanceToRootNode(parser);
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
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 {
// 1.实例layout的根节点的View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
// 2.生成根节点的params
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
// 3.设置params
temp.setLayoutParams(params);
}
}
......
// Inflate all children under temp against its context.
// 4.实例根节点子View
rInflateChildren(parser, temp, attrs, true);
......
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
// 5.是否添加到父容器中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
// 6.不添加到父容器中
if (root == null || !attachToRoot) {
result = temp;
}
}
}
......
return result;
}
}
inflate方法中的6点中的一些逻辑不难。
- 生成根节点实例
- 如果父容器不为null,生成LayoutParams
- 如果attachToRoot = true,设置temp的LayoutParams
- 编译子View
- 放入父容器中判断,放入就被父容器add
- 不放入父容器中判断,将temp赋值给result
最终返回result。这边能够总结的是,1: root为空没有生成layoutparams 2:attachToRoot决定的是是否被父容器addView。3:不加载到父容器 返回temp,加载到父容器中 返回result。
inflate方法中还有两个方法需要看,createViewFromTag和rInflateChildren方法。
2.createViewFromTag方法
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
......
try {
// 1.尝试创建View对象(支持部分系统提供的组件),不支持的直接返回null
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 2.createView 创建对象
if (-1 == name.indexOf('.')) {
//支持android.view.下的组件
view = onCreateView(context, parent, name, attrs);
} else {
//类似com.asdasd.dddd.GirlView
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
......
}
public View onCreateView(@NonNull Context viewContext, @Nullable View parent,
@NonNull String name, @Nullable AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(parent, name, attrs);
}
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
2.1.tryCreateView方法
//LayoutInflater.java
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
// 看上面的注释就感觉挺有气氛的,如果是blinklayout的话,直接返回该对象。这个View的效果是一闪一闪的,像是party中闪烁的灯光
return new BlinkLayout(context, attrs);
}
View view;
//factory,以AppCompatActivity为例,在oncreate中会被设置
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;
}
//AppCompatViewInflater.java
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
......
View view = null;
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;
......
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.
// createViewFromTag方法内通过反射实例,有兴趣可以自己看
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
2.2.createView方法
public final View createView(@NonNull Context viewContext, @NonNull String name,
@Nullable String prefix, @Nullable AttributeSet attrs)
throws ClassNotFoundException, InflateException {
......
//获取缓存中构造函数
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
//如果对应不上加载器 移除构造函数
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
if (constructor == null) {
// 不存在构造器,就反射获得构造器,并且放入到缓存中
// Class not found in the cache, see if it's real, and try to add it
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
mContext.getClassLoader()).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, viewContext, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, viewContext, attrs);
}
}
}
Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = viewContext;
Object[] args = mConstructorArgs;
args[1] = attrs;
try {
//反射实例出View对象
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
......
}
关键代码处有注释,不重复说了,到此 createViewFromTag方法就说完了,总结就是通过完整的包名+类名来反射实例对象出来。
3.rInflate方法/rInflateChildren方法
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
// 遍历所有子View
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
// 如果是include标签,需要获取layout文件资源后,重新进行解析。
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
// 解析include的layout资源。该方法内的代码跟我们一路上做的事情是一样的
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
// view组件
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 采用了递归的方式,去把该view的子view实例出来。
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}
能够看出来,最后调用了inflate方法。在该方法中对个别标签有做特殊处理。是include标签,就把include的layout资源重新走边,我们这一路上走过的路。如果是view,就实例出来并递归它的子view进行实例,添加进view中。
下面是parseInclude,大家可以看看,跟我们刚开始走区别在于是否要添加进父容器中,很显然,include到我们layout里,肯定是需要被我们添加进父容器中的。
private void parseInclude(XmlPullParser parser, Context context, View parent,
AttributeSet attrs) throws XmlPullParserException, IOException {
int type;
if (!(parent instanceof ViewGroup)) {
throw new InflateException("<include /> can only be used inside of a ViewGroup");
}
// Apply a theme wrapper, if requested. This is sort of a weird
// edge case, since developers think the <include> overwrites
// values in the AttributeSet of the included View. So, if the
// included View has a theme attribute, we'll need to ignore it.
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
final boolean hasThemeOverride = themeResId != 0;
if (hasThemeOverride) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
// If the layout is pointing to a theme attribute, we have to
// massage the value to get a resource identifier out of it.
int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
if (value == null || value.length() <= 0) {
throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
}
// Attempt to resolve the "?attr/name" string to an attribute
// within the default (e.g. application) package.
layout = context.getResources().getIdentifier(
value.substring(1), "attr", context.getPackageName());
}
// The layout might be referencing a theme attribute.
if (mTempValue == null) {
mTempValue = new TypedValue();
}
if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
layout = mTempValue.resourceId;
}
if (layout == 0) {
final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
throw new InflateException("You must specify a valid layout "
+ "reference. The layout ID " + value + " is not valid.");
}
final View precompiled = tryInflatePrecompiled(layout, context.getResources(),
(ViewGroup) parent, /*attachToRoot=*/true);
if (precompiled == null) {
final XmlResourceParser childParser = context.getResources().getLayout(layout);
try {
final AttributeSet childAttrs = Xml.asAttributeSet(childParser);
while ((type = childParser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty.
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(getParserStateDescription(context, childAttrs)
+ ": No start tag found!");
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)) {
// The <merge> tag doesn't support android:theme, so
// nothing special to do here.
rInflate(childParser, parent, context, childAttrs, false);
} else {
final View view = createViewFromTag(parent, childName,
context, childAttrs, hasThemeOverride);
final ViewGroup group = (ViewGroup) parent;
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.Include);
final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
final int visibility = a.getInt(R.styleable.Include_visibility, -1);
a.recycle();
// We try to load the layout params set in the <include /> tag.
// If the parent can't generate layout params (ex. missing width
// or height for the framework ViewGroups, though this is not
// necessarily true of all ViewGroups) then we expect it to throw
// a runtime exception.
// We catch this exception and set localParams accordingly: true
// means we successfully loaded layout params from the <include>
// tag, false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
try {
params = group.generateLayoutParams(attrs);
} catch (RuntimeException e) {
// Ignore, just fail over to child attrs.
}
if (params == null) {
params = group.generateLayoutParams(childAttrs);
}
view.setLayoutParams(params);
// Inflate all children.
rInflateChildren(childParser, view, childAttrs, true);
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
}
} finally {
childParser.close();
}
}
LayoutInflater.consumeChildElements(parser);
}
看完inflate方法后,我们可以知道很多,我们知道减少层级可以有效的优化,因为源码中是递归进行实例加载。一开始学习android时,我个人觉得这个类会参与渲染ui,在阅读源码之后明白了该类的作用1、进行解析layout文件;2、实例出view。
当然了我们还有看到中间实例view对象有经历一个factory,如果我们重写factory并且设置进入LayoutInflater中,再加上了解android获取资源的机制的话,我们就可以实现换肤机制。