View绘制流程(一) - LayoutInflater
1. LayoutInflater简介
1>:作用:动态的把 子View布局 添加到 父布局 中;
2>:获取LayoutInflater实例有2种方法:
public class LayoutInflaterActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_inflater);
// 获取LayoutInflater实例对象,有2种方法
// param1__要加载的布局id param2__给该布局的外部再嵌套一个父布局,如果不需要就传 null
// 方法1:
LayoutInflater inflater1 = LayoutInflater.from(this).inflate(resourceId, root) ;
// 方法2:
LayoutInflater inflater2 = (LayoutInflater)
this.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
.inflate(resourceId, root) ;
}
}
下边通过示例代码,演示把 Button布局 动态添加到 LinearLayout中;
2. 代码如下
场景:MainActivity的布局文件是 activity_main,里边就是一个空的LinearLayout布局,其余啥都没有;
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_layout"
>
</LinearLayout>
然后再定义一个布局文件 button_layout,就一个Button
<?xml version="1.0" encoding="utf-8"?>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
xmlns:android="http://schemas.android.com/apk/res/android" />
要做的是:用 LayoutInfater.inflate()方法 把button_layout添加到 activity_main中的LinearLayout中,代码如下:
public class LayoutInflaterActivity extends AppCompatActivity {
private LinearLayout mainLayout;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_inflater);
// LinearLayout 空的布局
mainLayout = (LinearLayout) findViewById(R.id.main_layout);
// 获取 LayoutInflater 对象
LayoutInflater inflater = LayoutInflater.from(this) ;
// 调用 inflater.inflate方法加载 button_layout 布局,
// null表示不给 button_layout布局外层添加父布局
View buttonLayout = inflater.inflate(R.layout.button_layout , null) ;
// 调用 LinearLayout的 addView()方法,把 buttonLayout 添加到 LinearLayout中
mainLayout.addView(buttonLayout);
}
}
效果如下:
![](https://img.haomeiwen.com/i5437668/e272761ef85a318e.png)
3. LayoutInflater源码分析
1>:获取LayoutInflater实例对象的2种方式:
// 方法1:
LayoutInflater inflater1 = LayoutInflater.from(this).inflate(resourceId, root) ;
// 方法2:
LayoutInflater inflater2 = (LayoutInflater)
this.getSystemService(Context.LAYOUT_INFLATER_SERVICE)
.inflate(resourceId, root) ;
源码如下:
// 这个是 方法1 LayoutInflater.from(this) 点击进来的,可以看到 方法1 就是对 方法2 的封装
public static LayoutInflater from(Context context) {
// 这个就是 方法2 写法
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
无论方法1还是方法2 , 点击 inflate()方法后,最终都会调用到下边方法:
// inflate()方法 用于 加载 button_layout 布局
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
try {
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 把节点名和参数传递进去,根据节点名创建view对象,在createViewFromTag()方法内部
// 会调用 createView()方法,通过反射创建view对象,并返回,在这里创建的是
// 根布局,然后返回
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
// rInflateChildren()方法:循环遍历这个根布局下的所有子元素,源码如下
rInflateChildren(parser, temp, attrs, true);
// ......省略一些代码
return result;
}
}
// 循环遍历 这个根布局 下的所有子元素
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
parseRequestFocus(parser, parent);
} else {
// 同样的调用 createViewFromTag()方法 通过反射创建 view 实例对象,并返回
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 同样的 递归调用 rInflateChildren()方法 查找 view这个根布局下的 所有 子view元素,每次
// 递归完成后 就把 查找出的 子view元素 添加到 父布局当中,这样把整个布局文件都解析完后
// 就形成一个 DOM树,最后会把 最顶层的根布局返回,到这里 inflater.inflate()方法加载结束
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
2>:动态的 把 子布局 添加到 父布局有3种方法:
// 方法1:
View layoutView = View.inflate(this , R.layout.button_layout , null) ;
// 方法2:
layoutView = LayoutInflater.from(this).inflate(R.layout.button_layout , null) ;
// 方法3:
layoutView = LayoutInflater.from(this).inflate(R.layout.button_layout , null , false) ;
分析:
1>:点击 方法1 的View.inflate()进入源码会发现,其实是调用的 第2种 方法:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
2>:点击 方法2 的LayoutInflater.from(this).inflate进入源码会发现,其实是调用的 第3种 方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
所以直接分析第3种方法就可以
3>: 分析第三种:LayoutInflater.from(this).inflate(R.layout.button_layout , null , false)
参数1:要加载的布局文件;
参数2:父布局容器;
参数3:true表示把布局文件添加到容器中,false表示不把布局文件添加到容器中
注意:
如果第三个参数为false,但是如果添加这句代码,表示和true一样,都可以把布局添加到容器中:
mainLayout.addView(buttonLayout);
开发中一般用第三种方式
4. 延伸知识点
现象:
对于上边的 button_layout布局文件中的 button,直接 给layout_width和layout_height 设置大小 是不会有作用的,比如:
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="300dp"
android:layout_height="80dp"
android:text="Button" >
</Button>
button 按钮大小不会有任何变化,平时在 xml布局文件中用的 layout_width和layout_height设置具体数值、 match_parent 或者 wrap_content等都是可以的,是因为xml布局文件中设置的控件 都是 有父布局的。
如果想要给某个 控件设置 layout_width和layout_height属性,就必须把 这个控件放到一个 父布局当中,如果没有父布局的存在,如上边的 Button,直接设置 宽高属性没有任何作用;
如果给 上边Button控件 外层添加一个 RelativeLayout,然后给 Button设置 宽高属性局可以,比如:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:layout_width="300dp"
android:layout_height="80dp"
android:text="Button" >
</Button>
</RelativeLayout>
延伸:
平时在 Activity中 的 activity_main或者其他Activity的布局文件,给最外层的根布局设置 宽高都是可以的,是因为 对于setContentView(R.layout.activity_main)方法的根布局,Android 会给它的根布局 最外层嵌套一个 FrameLayout,所以给 activity_main 最外层的根布局设置 宽高有作用,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/main_layout"
>
</LinearLayout>
public class LayoutInflaterActivity extends AppCompatActivity {
private LinearLayout mainLayout;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout_inflater);
mainLayout = (LinearLayout) findViewById(R.id.main_layout);
ViewParent parent = mainLayout.getParent();
// mainLayout 的父布局是:android.widget.ContentFrameLayout{android:id/content}
Log.e("TAG" , "mainLayout的父布局是:"+parent) ;
}
}
根据log日志可知:最外层 LinearLayout 的 父布局是 FrameLayout,而这个 FrameLayout是系统自动帮我们添加上的;
5. Activity界面的组成
任何一个 Activity 的界面都由2部分组成:标题栏 和 内容布局:
标题栏:每个Activity最顶部显示的标题栏内容,可以在代码中控制是否显示;
内容布局:是一个FrameLayout,它的id 叫做 content,平时在 Activity中 调用 setContentView(R.layout.activity_main)方法,其实就是把 activity_main 布局添加到 FrameLayout容器中的,对于上边的 activity_layout_inflater布局中的 标题栏和内容布局:
![](https://img.haomeiwen.com/i5437668/a2c9bb406ce49950.png)
Activity组成图如下:
![](https://img.haomeiwen.com/i5437668/e9f1539de4d296ff.png)