1.layout布局转换view方法
2个重载方法,最终都是到最后一个方法
LayoutInflater.from(this).inflate();
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, 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);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
2.真正的解析方法
Android里面的layout都是xml形式,因此出现XmlResourceParser解析器,用于解析xml布局各个节点(tag)view. 继续看方法inflate(parser, root, attachToRoot),很明显解析器作为参数传入方法进行解析。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
.......
View result = root;
try {
// Look for the root node. 此处检查根布局tag
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)) { //merge标签
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 {
// 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) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 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);
}
}
// Inflate all children under temp against its context. 递归调用解析view树
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) {
result = temp;
}
}
} catch (Exception e) {
.......
} finally {
.......
}
return result;
}
}
以下是几个关键点:
- root作为该布局view的父布局
View result = root;
- 生成临时的view,布局根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null; //初始布局参数
- 根据root测量布局根view的布局参数
final AttributeSet attrs = Xml.asAttributeSet(parser);
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
params = root.generateLayoutParams(attrs); //获取attrs布局参数
- 递归调用解析view树
rInflateChildren(parser, temp, attrs, true);
- root是否为null,attachToRoot是否为true三种组合
//此处root!=null,就获取布局里面的布局参数
if (root != null) {
// Create layout params that match root, if supplied params =
params = root.generateLayoutParams(attrs); //获取attrs布局参数
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// 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);
}
if (root == null || !attachToRoot) {
result = temp;
}
2.分析结论
上面的tempView代表整个layout布局转换出来的view,而最外层的根view,大小是由其自己和父view共同测量决定,也就是在父view里面的布局参数。
1.1 inflate(int resource, ViewGroup root)==inflate(resource, root, root != null)
1.2 inflate(int resource, ViewGroup root, boolean attachToRoot)
- 1.1等价1.2 root != null, attachToRoot=true
1.3 inflate(parser, root, attachToRoot)
root=null 则 ViewGroup.LayoutParams params = null , temp和根布局无关; //返回temp,没有任何父布局,不被加入任何root,而且没有设置布局参数,如果加入容器中,布局属性失效。
root!=null 且attachToRoot=true则 root.addView(temp, params);,返回root,temp布局属性生效,并且被加入root。
root!=null 且attachToRoot=fasle则 temp.setLayoutParams(params); 返回temp,不被加入root,设置了布局参数,如果加入view容器中,temp布局属性生效。
以上结论,大家可以自己验证,可以借助Layout Inpector查看布局的层级结构。
3. Android 源码出现的例子(基于7.0)
- listview的item布局填充
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder=new ViewHolder();
convertView = mInflater.inflate(R.layout.item, null);
}else {holder = (ViewHolder)convertView.getTag(); }
//只能填充以下的例子
1.convertView = mInflater.inflate(R.layout.item, parent,false);
2.convertView = mInflater.inflate(R.layout.item, null);
2.convertView = mInflater.inflate(R.layout.item, null,false);
这样添加会异常
convertView = mInflater.inflate(R.layout.item, parent);
@Override
public void addView(View child, LayoutParams params) {
throw new UnsupportedOperationException("addView(View, LayoutParams) "
+ "is not supported in AdapterView");
}
- Activity 的布局填充
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
//PhoneWindow实现
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent); //将布局view放入到DecorVew
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
将布局view放入到DecorVew
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
- Dialog
Dialog布局
final Window w = new PhoneWindow(mContext);
public void setContentView(@LayoutRes int layoutResID) {
mWindow.setContentView(layoutResID);
}
DecorView布局参数
mDecor = mWindow.getDecorView();
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
mWindowManager.addView(mDecor, l);