Android里面有很多场景会用到LayoutInflate这个类,我们通过这个类去解析指定的布局,然后展示在布局里面。api的调用是如此的简单,我们如果每次都是单纯的调用,那就无法得到提升了,所以现在我们来看一下这个流程究竟是怎么一回事。
先来看下用法跟场景:
@Override
public AllPavilionViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//在recyclerview/listview的适配器中 构建每个item的布局
return new AllPavilionViewHolder(inflater.inflate(R.layout.item_location_pavilion, parent, false));
}
public AmountView(Context context, AttributeSet attrs) {
super(context, attrs);
//自定义UI中 解析指定布局去依附在自定义view中
LayoutInflater.from(context).inflate(R.layout.ui_amount, this);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//观察源码 其实这个setContentView 内部也是调用Layoutflate.inflate的方式来构建view的
setContentView(R.layout.activity_reserve);
}
可能还有一些其他场景会用到这个方法,这些就不一一举例了,我们直接来通过源码分析这块的流程吧。
/**
* Obtains the LayoutInflater from the given context.
* //首先是初始化 可以看出我们常用的LayoutInflater其实是对如下方法的简单封装,意味着我们其实有两种方式来构建出LayoutInflater的实例
*/
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;//为什么变量名字大写开头 我也不知道
}
//拿到我们想要的实例之后,之后就是调用inflate方法去构建view了。
//四种inflate方法 到最后还是殊途同归
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
//里面调用的还是下面那个方法 只是做了一点判断而已
return inflate(resource, root, root != null);
}
//说一下参数
//resource代表需要解析的xml文件
//root 表示这个xml文件外围包裹的父布局,如果不需要 直接传null便可
//attachToRoot 是否需要布局文件依附在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) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);//得到一个xml解析器
try {
return inflate(parser, root, attachToRoot);//这个方法才是真正的开始解析布局文件
} finally {
parser.close();//解析器用完 关掉
}
}
//重点来了
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
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!");
}
//开始构建 root view
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//根布局为merge版
if (root == null || !attachToRoot) {
//判断根布局是不是merge 如果是的话 需要有父布局并且attachToRoot为true 否则抛出异常
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);//这里开始循环解析
} else {
//根布局为非merge版
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);//解析构造整个布局实例
ViewGroup.LayoutParams params = null;
if (root != null) {//如果root 不为null 然后attachToRoot 为false
//只有在这种情况下 他的属性才会被真正设置进去 否则无效 root!=null&&attachToRoot==false (会影响宽高跟margin ,关于padding 毕竟是作用在view的onDraw方法里面的,还是有效果的)
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
//开始解析布局文件中 子view 循环解析所有子view
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
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.
if (root == null || !attachToRoot) {//判断是返回刚构建的view 还是之前传进来的root
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
我们先看createViewFromTag方法
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
//这里才是主导
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {//默认ignoreThemeAttr 给的是false
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();//TypedArray用完都是要回收的
}
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);//一种闪烁的布局
//想要看效果 可以看http://blog.csdn.net/qq_22644219/article/details/69367150
}
try {
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);//主体还是这个方法 通过反射的方式创建了view
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
根view创建好了之后,之后会调用 rInflate(parser, root, inflaterContext, attrs, false)一步步的实现每一个子view
/**
* Recursive method used to descend down the xml hierarchy and instantiate
* views, instantiate their children, and then call onFinishInflate().
* 递归的调用方法构建整个xml的布局,沿层次一个个的实例化view
*/
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();//获取当前布局的深度
int type;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {//循环拿到那个start_tag
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
//对一些特殊的节点做处理
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);//这里做include内容的解析
} else if (TAG_MERGE.equals(name)) {//意思是 merge 不能作为一个子view 除非他是根布局
throw new InflateException("<merge /> must be the root element");
} else {
//这里做真正的创建view 还是通过反射的形式 另外把一些属性设置进去 再添加到父view中
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);
}
}
//在这里 所有的view 都被构建成功
if (finishInflate) {//当根布局不是merge 那就为true
parent.onFinishInflate();//就是一个回调吧
}
}
整个流程就是这样子 ,我们再来看一下,我在最初的时候说 setContentView也是通过LayoutInflate方法的:
@Override
public void setContentView(@LayoutRes int layoutResID) {//我们在activity里面的调用 就是这个方法
getDelegate().setContentView(layoutResID);
}
//这是一个抽象的方法
public abstract void setContentView(@LayoutRes int resId);
//然后我们看AppCompatDelegateImplV9的实现
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//最后 还是调用到这个方法了
mOriginalWindowCallback.onContentChanged();
}
总结一下LayoutInflate里面的实现,其实也是很简单的用了android提供的pull解析一步步的解析下来的,里面的每一个节点就构建成一个view了(通过反射),从根布局开始一层层的解析构建,最终形成一个完整的DOM结构,然后把根布局的引用传出去,这样inflate方法就成功完成了。
最后说一下三个inflate的三个参数作用,毕竟有时候会疑惑该如何传参:
- 如果root为null,attachToRoot将失去作用,设置任何值都没有意义。
- 如果root不为null,attachToRoot设为true,则会给加载的布局文件的指定一个父布局,即root,此时是root设置的那些宽高跟margin ,是没有效果的。
- 如果root不为null,attachToRoot设为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view当中时,这些layout属性会自动生效。
- 在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默认为true。
还有一点,请不要在listview\recyclerview里面构建每个item的时候设置root!=null 并且attachToRoot又给了true ,这样会直接报错的,因为
@Override
public void addView(View child) {
throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
}
参考:
Android LayoutInflater原理分析,带你一步步深入了解View(一)
Android LayoutInflate深度解析 给你带来全新的认识