android 自定义View 三 ---- LayoutInflater

说到自定义view就不得提到LayoutInflater,虽然我们在代码中可以直接用new方法构造出各种View,然后再添加各种属性去控制View的大小和位置等布局,但是这是很复杂繁琐的,细节优化更麻烦困难,面对复杂布局,用代码构造更显得无力。这时我们必须借助于LayoutInflater这个神器了。

LayoutInflater的作用就是能够将value/layout目录下的xml布局文件,实例化成相应的View。它的好处是显而易见的,xml布局文件我们可以可视化构造。要使用LayoutInflater,首先要得到LayoutInflater的实例对象,有下面三种方法:

方法一:

在activity里调用:

LayoutInflater inflater = getLayoutInflater();

方法二:

LayoutInflater inflater = LayoutInflater.from(mContext);

方法三:
LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
如果查看android源码可以知道前面两个方法最后都是调用到第三个方法。有了LayoutInflater对象就可以调用它
的inflate方法把xml布局文件变成view了。

下面我们就通过一个非常简单的小例子,来更加直观地看一下LayoutInflater的用法。比如说当前项目MainActivity对应的布局文件叫做activity_main.xml,代码如下所示:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/root_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

代码很简单,就只有一个RelativeLayout,里面什么控件都不放,我们再建一个layout_textview.xml,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!!!"
    android:textSize="18sp"
    android:padding="10dp"
    android:gravity="center"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/></span>

代码也很简单,就一个TextView,现在我们看看用layoutInflater把这个textview加载到MainActivity的布局里,代码如下:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = this;
        RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.root_layout);
        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        TextView textView = (TextView) inflater.inflate(R.layout.layout_textview,null);
        mainLayout.addView(textView);
    }
这里使用了LayoutInflater的inflate(int resource, ViewGroup root),进入步看看源码实际上是调用了:

    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();
        }
    }
从上面可以看出来,inflate方法实际有三个参数:

resource :  int型,将被实例化的布局文件

root :  ViewGroup型,根布局或父布局,如果这个参数不为空,则传入的viewgroup是将被实例化的View的根布局。
attachToRoot : boolean型,是否依附到传入的根布局上 


先不多说,我们先看看上面的代码的效果:


通过inflater.inflate(R.layout.layout_textview,null)方法,说明确实把TextView实例化出来了,当想我们修改下TextView的大小,布局文件做如下修改:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:text="Hello World!!!"
    android:textSize="18sp"
    android:padding="10dp"
    android:gravity="center"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/>
我们修改了layout_width把wrap_content改成了match_parent,让宽度充满全屏,也增加了layout_height的高度,再运行,看看效果:


OH,NO!竟然没有变化,不是我们设想的宽度会充满全屏,我们设置的layout_width和layout_height没有起到一点作用。回到上面inflate方法,它有三个参数,前面root参数我们传进去的是null,我们修改下这个参数,看看是不是它的问题,我们将activity代码里的inflater.inflate(R.layout.layout_textview,null)改成:inflater.inflate(R.layout.layout_textview,mainLayout,false); 再运行,效果如下:



哈哈,终于达到了我们想要的效果,这说明使用inflate时,传入的布局文件“最外层”的layout_height和layout_width是要有root布局才能生效,这样也说明其实layout_height和layout_width是跟父布局有关,xml布局里也并没有设置view的height和width两个属性。

再回来看看inflate的第三个参数attachToRoot,它其实代表实例化view后,是否将这个view依附添加到传入的第二个参数的root父布局里。即如果为true,省去了我们手动再调用addView()方法了。

下面我们分析下inflate的源码:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        //生成xml解析器
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            
         final Context inflaterContext = mContext;
         final AttributeSet attrs = Xml.asAttributeSet(parser);//获取xml里面属性
         View result = root;

         final String name = parser.getName(); //得到布局xml里的根布局的名称,这里是textview
              
         if (TAG_MERGE.equals(name)) {//如果是merge布局文件
              if (root == null || !attachToRoot) { //这里attachToRoot必须为true
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
              }
              rInflate(parser, root, inflaterContext, attrs, false);
         } else {
              //这里先生成上面传入的布局文件里的根View,我们上面只传入了一个TextView
              // 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) {
                  //如果root不为空,这里会根据root生成layoutParams
                  params = root.generateLayoutParams(attrs);
                  if (!attachToRoot) {
                        //如果attachToRoot为false,将上面生成的params赋值给生成的View
                        temp.setLayoutParams(params);
                  }
               }
               //上面生成了根View,再生成它的子View
               rInflateChildren(parser, temp, attrs, true);

               if (root != null && attachToRoot) {
                   root.addView(temp, params); //加入的传入的root布局里
               }

               if (root == null || !attachToRoot) {
                    result = temp;
               }
            return result;
        }
    }
从上面源码可以看出inflate()方法里会根据传入的root父布局生成相应的layoutParams,然后在赋值给新的View.比如root布局是RelativeLayout,则会生成RelativeLayout.LayoutParams属性,那么我们再修改下我们的布局文件验证下我们的分析:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="150dp"
    android:text="Hello World!!!"
    android:textSize="18sp"
    android:padding="10dp"
    android:gravity="center"
    android:layout_alignParentBottom="true"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/>
我们这次加入了layout_alignParentBottom参数,这个参数只有在RelativeLayout里才有用,在我们这里看看有没有作用:


这里起到了作用,说明确实是根据父控件生成了相应的LayoutParams。顺便提下我们在activity里面setContentView设置布局文件,最终也是调用到了LayoutInflater类的inflate方法。

下面总结下:

inflate方法有三个参数:

resource :  int型, 传入将被实例化的布局文件

root :     ViewGroup型,根布局或父布局,如果这个参数不为空,则会为生成的view加上root相应的layoutParams参数
attachToRoot : boolean型,是否依附到传入的root根布局上 



更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号:  Android老鸟

                                                
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值