LayoutInflater简介
LayoutInflater 顾名思义就是布局填充器,做过Android界面编程,相信对这个类都比较熟悉,可能有人说,我们在activity中使用setContentView(Id)来初始化布局,但实际上其内部也是使用LayoutInflater 来填充布局的。可以通过以下两种方式获取LayoutInflater1. LayoutInflater layoutInflater = LayoutInflater.from(context);2. LayoutInflater layoutInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE)然后再调用 layoutInflater.inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) 来获取View可能有的同学使用View.inflate()获取View的,那我们进去看下:public static View inflate(Context context, int resource, ViewGroup root) {LayoutInflater factory = LayoutInflater.from(context);return factory.inflate(resource, root);}其实最终还是通过LayoutInflater 来填充布局的通过LayoutInflater.inflate()填充布局,主要有如下填充方法:
最终都会调用到 inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)。
1、使用setContentView(View)
<Button xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="200dp"android:text="测试按钮" />public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//当使用inflate(layout, null)时,layout最外层的控件的宽高是没有效果的,所以最外层控件宽高会变成match_parentView view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);//布局前view没有父控件,此时view.getParent()及view.getLayoutParams()都为空Toast.makeText(this, "布局前是否没有父布局:" + (view.getParent() == null), Toast.LENGTH_SHORT).show();//truesetContentView(view);Toast.makeText(this, "布局后父布局是否为FrameLayout:" + (view.getParent() instanceof FrameLayout) + "", Toast.LENGTH_SHORT).show();//true}}结果:这个按钮的大小占了全屏。解释:因为在布局时,View没有父控件,所以view(即Button)的 layout_width 等属性是没有效果的。注意:在setContentView(***)的时候,系统会默认的加一个父布局Framlayout,这也就是为什么叫setContentView而不是叫setView的原因。下面的效果及原理和上面一样,在没有父控件时,setLayoutParams也是没有效果的。
public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button view = new Button(this);view.setText("代码中创建View");RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, 200);view.setLayoutParams(layoutParams);Toast.makeText(this, "布局前是否没有父布局:" + (view.getParent() == null), Toast.LENGTH_SHORT).show();//truesetContentView(view);Toast.makeText(this, "布局后父布局是否为FrameLayout:" + (view.getParent() instanceof FrameLayout) + "", Toast.LENGTH_SHORT).show();//true}}
2、使用setContentView(ViewGroup)
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="300dp"android:background="#f00" ><Buttonandroid:layout_width="wrap_content"android:layout_height="200dp"android:text="测试按钮" /></RelativeLayout>public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);View view = LayoutInflater.from(this).inflate(R.layout.activity_main, null);//当使用inflate(layout, null)时,layout最外层的控件的宽高是没有效果的,所以最外层控件宽高会变成match_parent。但是内层控件的宽高还是有效的。Toast.makeText(this, "view是否是xml中的父布局:" + (view instanceof RelativeLayout), Toast.LENGTH_SHORT).show();//truesetContentView(view);Toast.makeText(this, "布局后view的父布局是否为FrameLayout:" + (view.getParent() instanceof FrameLayout) + "", Toast.LENGTH_SHORT).show();//true}}结果:背景全屏都是红色,但按钮的大小和我们希望的一致。解释:因为Button外部有一个父控件,所以Button的layout_width等属性有效果;而RelativeLayout外部没有父控件,所以其layout_width等属性没有效果。注意:仅仅是layout_***等属性没效果,但是background等属性还是有效果的。下面的效果及原理和上面一样public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button view = new Button(this);view.setText("代码中创建View");RelativeLayout relativeLayout = new RelativeLayout(this);LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 200 * 3);//注意,view.setLayoutParams(layoutParams)方法中的参数是作用到view上的,在view没有父布局时用哪个Layout的LayoutParams都可以view.setLayoutParams(layoutParams);relativeLayout.addView(view);//view.setLayoutParams(layoutParams);//将view添加到RelativeLayout后,就不能再使用其他Layout的LayoutParams了,否则报类型转换异常Toast.makeText(this, "布局前父布局是否为RelativeLayout:" + (view.getParent() instanceof RelativeLayout) + "", Toast.LENGTH_SHORT).show();//truesetContentView(relativeLayout);Toast.makeText(this, "布局后父布局的父布局是否为FrameLayout:" + (view.getParent().getParent() instanceof FrameLayout) + "", Toast.LENGTH_SHORT).show();//true}}
3、使用setContentView(Int layout)
<Button xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="200dp"android:text="测试按钮" />public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}}结果:这个按钮的大小和我们希望的一致。解释:setContentView(Int layout)方法和其他重载方法不太一样,他会先把xml中的布局加到根部的FrameLayout上再进行布局,所以在布局时Button是由父布局的。下面的效果和道理和上面的一样
public class MainActivity extends Activity {protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Button view = new Button(this);view.setText("代码中创建View");RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, 200 * 3);setContentView(view, layoutParams);//注意,button的父布局不是上面定义layoutParams时用的RelativeLayout,而是系统默认加上的FrameLayoutToast.makeText(this, "布局后父布局是否为FrameLayout:" + (view.getParent() instanceof FrameLayout) + "", Toast.LENGTH_SHORT).show();//true}}
总结
首先,方法 inflate(int resource, ViewGroup root)会调用inflate(int resourceId, ViewGroup root, boolean attachToRoot)方法,并且如果root不为null,则attachToRoot 为true。public View inflate(int resource, ViewGroup root) {return inflate(resource, root, root != null);}下面我们来分析inflate(int resourceId, ViewGroup root, boolean attachToRoot)中这三个参数的作用首先:resourceId 就是布局Id,root是这个布局所依附的父布局, attachToRoot就是是否依附在这个父布局上,下面分情况讨论下。
- root 不为null,attachToRoot 为 false:resource的最外层的控件的宽高是有效果的,但不会添加到父控件中
- root 不为null,attachToRoot 为 true: resource的最外层的控件的宽高是有效果的,并且会添加到父控件中
- root 若为null,此时 attachToRoot 的值将不起作用,resource的最外层的控件的宽高是【没有】效果的,并且会添加到父控件中
为什么呢?大家再回忆一下,我们怎么动态修改View的宽和高,代码如下:View view = findViewById(R.id.btn);***Layout.LayoutParams layoutParams = (***Layout.LayoutParams) view.getLayoutParams();layoutParams.width = 200 * 2;layoutParams.height = 200 * 2;view.setLayoutParams(layoutParams);
为什么不是直接获取View设置View的大小而是去设置View的 LayoutParams呢?这是因为我们在设置一个View的layout_width、layout_height、padding 的值的时候,其实这些属性都是作用在父布局中的,并不是作用于View上的,这也是为什么叫layout_width而不是width的原因。所以若没有父布局,你设置的这些layout_***属性都是无效的。但若你xml中是一个多层的布局,则只是最外层的这些属性无效,里面控件的属性还是有效的。