1、基本介绍
在开发中 LayoutInflater. inflate() 这个方法还是非常有用的,它的作用类似于 findViewById()。不同点是 inflate() 是用来找 res/layout/ 下的 xml 布局文件,并且实例化,而 findViewById() 是找 xml 布局文件下的具体 widget 控件(如 Button、TextView 等)。
对于一个没有被载入或者想要动态载入的界面,都需要使用 LayoutInflater.inflate() 来载入。而对于一个已经载入的界面,就可以使用 Activity.findViewById() 方法来获得其中的界面元素。
2、获取LayoutInflater实例
获得 LayoutInflater 实例的几种方式:
1.
LayoutInflater inflater = getLayoutInflater(); //调用Activity的getLayoutInflater()
2.
LayoutInflater localinflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
3.
LayoutInflater inflater = LayoutInflater.from(context);
其实,这三种方式本质是相同的。第一种方式调用的 getLayoutInflater() 调用了 PhoneWindow 的 getLayoutInflater() 方法,从其源码看:
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
可以看到它又调用了第二种方法的方法,我们接着看 from(Context) 方法的源码:
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;
}
所以这三种方式最终本质是都是调用的 Context.getSystemService()。
3、inflate()介绍
除了 LayoutInflater.inflate() 还有 View.inflate() 这个方法,我们看看它的源码:
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
可见 View.inflate() 是通过 LayoutInfater.inflate() 实现了 xml 文件到 View 或者 Widget 的转化。
inflate() 有几种加载方式,我们常用的是如下两种:
public View inflate (int resource, ViewGroup root)
public View inflate (int resource, ViewGroup root, boolean attachToRoot)
所以我们就来介绍这两种方法,resource 这个参数因为我们之前说过 inflate() 加载 res/layout 下的 xml 布局文件,所以这个是 xml 布局文件的 id。
root 的英文解释是根,所以这个参数的值是 resource 的父 View。inflate() 这个方法就是将 resource 布局添加到 root 父布局里。而 attachToRoot 这个参数是控制是否添加布局,true 为添加,false 则不添加。
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</LinearLayout>
item_btn.xml:
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/btn"
android:text="inflate"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</Button>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout layout = (LinearLayout) findViewById(R.id.ll);
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.item_btn, layout);
}
}
可以看到在主布局里加载了 item.xml。现在我们为 inflate() 方法加上 attachToRoot 这个参数并为它赋值 false。
这样就取消加载 item_btn.xml 了。这时候如果我们在下面有调用 addView() 方法再将 Button 添加,Button 就有会出现在界面上了。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout layout = (LinearLayout) findViewById(R.id.ll);
LayoutInflater inflater = LayoutInflater.from(this);
Button button = (Button) inflater.inflate(R.layout.item_btn, layout, false);
layout.addView(button);
}
}
布局文件转换为 View,不都是为了放在组件里,显示在界面上吗?为何还要多此一举,分两步来做这样的事情呢?Android 中有些组件有其自身的“add View”机制,如:Fragment.onCreateView。
public class CustomFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_fragment, container, false);
return view;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.ll, new CustomFragment());
transaction.commit();
}
}
“add View”的逻辑由 FragmentTransaction 进行处理了,如果我们这里将 attachToRoot 设置为true,就会有 IllegalStateException。所以组件自己有“add View”的机制,我们就不需要也不能再设置为 true啦。
4、设置root
大家可能会有疑问既然没有将创建的 View 添加到 root 中,为什么还要添加 root 参数呢?直接使用如下代码:
View view = inflater.inflate(R.layout.item_btn, null);
这样不是更简单吗?但如果这样写,lint 会给出警告 。xml 布局文件在解析成 View 的时候,需要 root 父布局的布局信息(View 的布局参数要受到 root 的限制)。如果不填写 root,使用 xml 布局文件生成 View 的时候就会使用默认的 LayoutParams。而这个不一定是符合要求的,View 可能比设定的要小。
有些时候无法明确的知道 View 的 root,当我们实例化一个 AlterDialog时:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LayoutInflater inflater = LayoutInflater.from(this);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
View customView = inflater.inflate(R.layout.item_btn, null);
dialogBuilder.setView(customView);
dialogBuilder.show();
}
}
在这种情况下,传递 null 就可以了。通用的规则是,如果可以知道root,一定要传递这个值给参数。
综上所述,我们应当注意:
- 如果知道 root,一定要传,尽量避免传 null。
- 当不需要将布局文件生成的 View 添加到组件中时(组件有其自身的添加逻辑),attachToRoot 设置成 false。
- 当 View 已经添加到一个 root 中时,attachToRoot 设置成 false。
- 自定义组件应该将 attachToRoot 设置成true。
结束语:本文仅用来学习记录,参考查阅。