美文网首页
LayoutInflater.inflate方法的参数解析

LayoutInflater.inflate方法的参数解析

作者: Rimson | 来源:发表于2018-11-19 20:18 被阅读0次

    发现一个小问题

    项目中需要加载xml文件时,经常使用下面这个方法
    (其实View.inflate,setContentView内部都是用LayoutInflater实现的)
    View view = LayoutInfalter.from(this).inflate(**.xml, null);

    其中第二个root参数显示为@Nullable,但是实际上传入null时,又会飘黄警告:

    Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) less... (Ctrl+F1)
    When inflating a layout, avoid passing in null as the parent view, since otherwise any layout parameters on the root of the inflated layout will be ignored.
    避免将null作为root参数传入该方法(因为传入null会使根部局的layout参数失效)

    接下来就学习一下这个警告是什么意思

    源码分析

    Android官方API给出了四个方法

    // 第一个方法
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
            return inflate(resource, root, root != null);
        }
    
    //第二个方法
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
            return inflate(parser, 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();
            }
        }
    

    可以发现最终这些方法都会调用第三个方法,所以我们只需要关注这个方法。其中,重要的部分可以概括如下

    final AttributeSet attrs = Xml.asAttributeSet(parser);
    View result = root;
    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
    temp.setLayoutParams(params);
    if (root != null && attachToRoot) {
      root.addView(temp, params);
    }
    if (root == null || !attachToRoot) {
      result = temp;
    }
    
    return result;
    

    这段代码首先用result获取了可能存在的父视图,如果root为null,result就向下返回当前view;如果root不为null,就把当前view添加到ViewGroup中,最终形成一个DOM结构,最后把DOM树最顶层的根部局返回。

    总结得到了以下结论:

    1. 如果root为null,attachToRoot将失去作用;
    2. 如果root不为null,attachToRoot为true,则会将当前view添加到父view中;
    3. 如果root不为null,attachToRoot为false,则会将布局文件最外层的所有layout属性进行设置,当该view被添加到父view的时候,父view的最外层layout属性将会改为子view的layout属性;

    实例验证

    首先定义parent.xml为父视图:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/fatherLinearLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:background="#00ff00" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="这是父视图"/>
    </LinearLayout>
    

    child.xml为子视图:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="200dp"
        android:layout_height="400dp"
        android:orientation="vertical"
        android:background="#ffff00" >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="这是子视图"/>
    </LinearLayout>
    

    inflate(resource, null)

    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            View parentView = LayoutInflater.from(this).inflate(R.layout.parent, null);
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
            if (layoutParams != null){
              Log.d(TAG, "before:" + layoutParams.width);
              Log.d(TAG, "before:" + layoutParams.height);
            }
            parentView = LayoutInflater.from(this).inflate(R.layout.child, null);
            layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
            if (layoutParams != null){
              Log.d(TAG, "after:" + layoutParams.width);
              Log.d(TAG, "after:" + layoutParams.height);
            }
            setContentView(parentView);
        }
    }
    

    运行结果:

    Logcat中没有任何输出信息,表明inflate前后的layout属性均为null
    这是因为resource外层没有root,导致layout属性失效,因此默认match_parent;


    inflate(resource, root)

    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            View parentView = LayoutInflater.from(this).inflate(R.layout.parent, null);
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
            if (layoutParams != null) {
              Log.d(TAG, "before:" + layoutParams.width);
              Log.d(TAG, "before:" + layoutParams.height);
            }
            parentView = LayoutInflater.from(this).inflate(R.layout.child, (ViewGroup) parentView);
            layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
            if (layoutParams != null) {
              Log.d(TAG, "after:" + layoutParams.width);
              Log.d(TAG, "after:" + layoutParams.height);
            }
            setContentView(parentView);
        }
    }
    

    运行结果:


    Logcat中同样没有任何输出信息,这说明inflate(resource, root)和inflate(resource, root, true)只是将resource加到了root之下,并没有改变最外层的layout属性。


    inflate(resource, root, false)

    public class MainActivity extends AppCompatActivity {
        private static final String TAG = "MainActivity";
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            View parentView = LayoutInflater.from(this).inflate(R.layout.parent, null);
            LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
            if (layoutParams != null) {
              Log.d(TAG, "before:" + layoutParams.width);
              Log.d(TAG, "before:" + layoutParams.height);
            }
            parentView = LayoutInflater.from(this).inflate(R.layout.child, (ViewGroup) parentView, false);
            layoutParams = (LinearLayout.LayoutParams) parentView.getLayoutParams();
            if (layoutParams != null) {
              Log.d(TAG, "after:" + layoutParams.width);
              Log.d(TAG, "after:" + layoutParams.height);
            }
            setContentView(parentView);
        }
    }
    

    运行结果:


    输出信息如下:
    2018-11-17 17:28:54.567 4036-4036/com.rimson.c.inflatepractice D/MainActivity: after:600
    2018-11-17 17:28:54.567 4036-4036/com.rimson.c.inflatepractice D/MainActivity: after:1200

    说明infalte执行后,把子view的layout参数赋给了父view。


    总结

    • root是给当前要填充的resource外层再加上的一层ViewGroup;
    • 在不设置attachToRoot参数的情况下,这一参数的取值默认为(root != null);
    • layout参数指的是layout_xxx,例如background等属性仍然有效;
    1. 如果root为null,attachToRoot将没有意义,此时返回xml的根节点。且该根节点设置任何layout参数都没有意义。这很好理解,因为layout参数是描述自身在父控件或者容器下的属性,这时候没有容器,当然就失效了。
    2. 如果root不为null,attachToRoot设为true,则会给resource指定一个父布局root,即执行root.addView(resource, params),然后返回root,且root的layout参数会保留。
    3. 如果root不为null,attachToRoot设为false,则会将root最外层的layout属性设置为resource最外层的layout属性,即执行resource.setLayoutParams(params),然后返回resource。相当于root只提供布局参数。
    4. 如果可以传递root的情况下,就选择传递root;避免传入null,否则layout参数会失效。

    相关文章

      网友评论

          本文标题:LayoutInflater.inflate方法的参数解析

          本文链接:https://www.haomeiwen.com/subject/qvzafqtx.html