LayoutInflater布局服务
LayoutInflater
1、什么是LayoutInflater?
Layout是什么?
答:一个用于加载布局的系统服务,就是实例化与Layout XML文件对应的View对象,不能直接使用,需要通过getLayoutInflater( )方法或getSystemService( )方法来获得与当前Context绑定的LayoutInflater实例!
说到布局,大家第一时间 可能想起的是写完一个布局的xml,然后调用Activity的setContentView()
加载布局,然后把他显示 到屏幕上是吧~其实这个底层走的还是这个LayoutInflater。
在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById()
。不同点是:
- LayoutInflater是用来找
res/layout/下的xml
布局文件,并且实例化; - 而findViewById()是找xml布局文件下的具体
widget控件
(如 Button、TextView等)。
具体作用:
- 1、对于一个没有被载入或者想要动态载入的界面,都需要使用LayoutInflater.inflate()来载入;
- 2、对于一个已经载入的界面,就可以使用Activiyt.findViewById()方法来获得其中的界面元素。
2、如何调用inflate()函数
LayoutInflater.inflate()
这个方法,大家一定很熟悉——在给fragment添加布局文件,或者在RecyclerView的Adapter
中item添加布局时,都会用到。
如何调用inflate()函数?
- 首先要获得一个LayoutInflater的实例,有三种方法,这三种方法实质上是相同的,最常用的是第一种。
LayoutInflater inflater1 = LayoutInflater.from(this);
LayoutInflater inflater2 = getLayoutInflater();
LayoutInflater inflater3 = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
- 然后使用这个实例调用
inflate
方法
inflater.inflate(xxxxxxx)
3、inflate()方法参数解析
1、三个参数
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
- 第一个参数:要获取的布局(加载的布局对应的资源id),传入
R.layout.xxx
- 第二个参数:这个参数也是一个布局,是为第一个参数指定的父布局。如果不需要的话,写null就可以了!
- 第三个参数:是否为加载的布局文件的最外层套一层root布局,不设置该参数的话, 如果root不为null的话,则默认为true 如果root为null的话,attachToRoot就没有作用了! root不为null,attachToRoot为true的话,会在加载的布局文件最外层嵌套一层root布局; 为false的话,则root失去作用! 简单理解就是:是否为加载的布局添加一个root的外层容器~!
- true:将第一个参数表示的布局添加到第二参数的布局中。
- false:不将第一个参数表示的布局添加到第二参数的布局中。
既然不添加,那么为什么第二个参数不设置为null呢。
- 不添加的话,这个函数就只剩下一个作用了,那就是获取布局,为了使第一个参数的宽高属性不失效,所以要为他指定一个父布局。
具体可以参考源码:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
mConstructorArgs[0] = mContext;
View result = root;
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
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, attrs);
} else {
View temp = createViewFromTag(name, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
rInflate(parser, temp, attrs);
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
}
return result;
}
}
总结一下,就是:
- 若attachToRoot为true且root不为null,则调用root.addView()方法
- 若root为null,或者attachToRoot为false,则直接将temp赋于result(temp是通过root构造的,result就是root)
2、两个参数
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
- 第一个参数:要获取的布局,传入R.layout.xxx
- 第二个参数:这个参数也是一个布局,是为第一个参数指定的父布局。
- 如果这个参数是null就不把第一个参数的布局添加进来
- 如果这个参数不是null就把第一个参数的布局添加进来
查看源码它其实还是调用三个参数的
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root)
{
return inflate(resource, root, root != null);
}
3.1、attachToRoot何时为true,何时为false?
就拿我们的Adapter来说吧,在创建item布局时,有下列几种情况:
inflate(R.layout.xxx,null);
inflate(R.layout.xxx,parent,false);
inflate(R.layout.xxx,parent,true);
那么就讲一下这三种情况把。
首先,inflate(R.layout.xxx,null)
。这是最简单的写法,这样生成的布局就是根据http://R.layout.xxx
返回的View
。要知道,这个布局文件中的宽高属性都是相当于父布局而言的。由于没有指定parent,所以他的宽高属性就失效了,因此不管你怎么改宽高属性,都无法按你想象的那样显示。
然后,inflate(R.layout.xxx,parent,false)
。相较于前者,这里加了父布局,不管后面是true还是false,由于有了parent,布局文件的宽高属性是有依靠了,这时候显示的宽高样式就是布局文件中的那样了。
最后,inflate(R.layout.xxx,parent,true)
。这样……等等,报错了???哦,不要惊奇,分析一下原因:首先,有了parent,所以可以正确处理布局文件的宽高属性。然后,既然attachToRoot为true
,那么根据上面的源码就会知道,这里会调用root的addView
方法。而如果root是listView
等,由于他们是继承自AdapterView的,看看AdapterView的addView方法:
@Override
public void addView(View child) {
throw new UnsupportedOperationException("addView(View) is not supported in AdapterView");
}
不支持啊,那好吧,如果换成RecyclerView呢?还是报错了,看看源码:
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
现在知道了吧,adpater里面不要用true。那么什么时候用true呢?答案是fragment。在为fragment创建布局时,如果为true,那么这个布局文件就会被添加到父activity中盛放fragment的布局中。
3.2、LayoutInflater.from这个方法什么意思?
从一个Context中,获得一个布局填充器,这样你就可以使用这个填充器来把xml布局文件转为View对象了。
//加载布局管理器
LayoutInflater inflater = LayoutInflater.from(context);
//将xml布局转换为view对象
convertView = inflater.inflate(R.layout.item_myseallist,parent, false);
//利用view对象,找到布局中的组件
convertView.findViewById(R.id.delete);
因为在一个Activity里如果直接用findViewById()的话,对应的是setConentView()的那个layout里的组件
.
因此如果你的Activity里如果用到别的layout,比如对话框上的layout,你还要设置对话框上的layout里的组件(像图片ImageView,文字TextView)上的内容,你就必须用inflate()先将对话框上的layout找出来,然后再用这个layout对象去找到它上面的组件,如:
View view = View.inflate(this, R.layout.dialog_layout, null);
TextView dialogTV = (TextView) view.findViewById(R.id.dialog_tv);
dialogTV.setText("abcd");
如果组件R.id.dialog_tv
是对话框上的组件,而你直接用this.findViewById(R.id.dialog_tv)
肯定会报错.
4、Java代码加载布局使用流程
Step 1:
①创建容器:LinearLayout ly = new LinearLayout(this);
②创建组件:Button btnOne = new Button(this);
Step 2:
可以为容器或者组件设置相关属性:
- 比如:
LinearLayout
,我们可以设置组件的排列方向:ly.setOrientation(LinearLayout.VERTICAL)
; - 而组件也可以:比如
Button:btnOne.setText("按钮1")
; - 关于设置属性的方法可参见Android 的API,通常xml设置的属性只需在前面添加:set即可,比如
setPadding(左,上,右,下)
;
Step 3:
将组件或容器添加到容器中,这个时候我们可能需要设置下组件的添加位置,或者设置他的大小: 我们需要用到一个类:LayoutParams,我们可以把它看成布局容器的一个信息包!封装位置与大小 等信息的一个类!先演示下设置大小的方法:(前面的LinearLayout可以根据不同容器进行更改)
LinearLayout.LayoutParams lp1 = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
很简单,接着就到这个设置位置了,设置位置的话,通常我们考虑的只是RelativeLayout! 这个时候用到LayoutParams
的addRule( )方法
!可以添加多个addRule( )哦! 设置组件在父容器中的位置,
比如设置组件的对其方式:
RelativeLayout rly = new RelativeLayout(this);
RelativeLayout.LayoutParams lp2 = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp2.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
Button btnOne = new Button(this);
rly.addView(btnOne, lp2);
参照其他组件的对其方式: (有个缺点,就是要为参考组件手动设置一个id,是手动!!!) 比如:设置btnOne居中后,让BtnTwo位于btnOne的下方以及父容器的右边!
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RelativeLayout rly = new RelativeLayout(this);
Button btnOne = new Button(this);
btnOne.setText("按钮1");
Button btnTwo = new Button(this);
btnTwo.setText("按钮2");
// 为按钮1设置一个id值
btnOne.setId(123);
// 设置按钮1的位置,在父容器中居中
RelativeLayout.LayoutParams rlp1 = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
rlp1.addRule(RelativeLayout.CENTER_IN_PARENT);
// 设置按钮2的位置,在按钮1的下方,并且对齐父容器右面
RelativeLayout.LayoutParams rlp2 = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
rlp2.addRule(RelativeLayout.BELOW, 123);
rlp2.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
// 将组件添加到外部容器中
rly.addView(btnTwo, rlp2);
rly.addView(btnOne, rlp1);
// 设置当前视图加载的View即rly
setContentView(rly);
}
}
step 4:
调用setContentView( )
方法加载布局对象即可! 另外,如果你想移除某个容器中的View,可以调用容器.removeView(要移除的组件);
运行截图:
5、Java代码动态加载xml布局
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/RelativeLayout1"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/btnLoad"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="动态加载布局"/>
</RelativeLayout>
inflate.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:id="@+id/ly_inflate" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是Java代码加载的布局" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我是布局里的一个小按钮" />
</LinearLayout>
接着到我们的MainActivity.java在这里动态加载xml布局:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获得LayoutInflater对象;
final LayoutInflater inflater = LayoutInflater.from(this);
//获得外部容器对象
final RelativeLayout rly = (RelativeLayout) findViewById(R.id.RelativeLayout1);
Button btnLoad = (Button) findViewById(R.id.btnLoad);
btnLoad.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//加载要添加的布局对象
LinearLayout ly = (LinearLayout) inflater.inflate(
R.layout.inflate, null, false).findViewById(
R.id.ly_inflate);
//设置加载布局的大小与位置
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
lp.addRule(RelativeLayout.CENTER_IN_PARENT);
rly.addView(ly,lp);
}
});
}
}
参考
1、https://www.runoob.com/w3cnote/android-tutorial-layoutinflater.html
2、https://zhuanlan.zhihu.com/p/23334059