zoukankan      html  css  js  c++  java
  • android View的布局宽高之LayoutInflater函数详解

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:id="@+id/main_layout"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
    </LinearLayout>  
    
    

    这个布局文件的内容非常简单,只有一个空的LinearLayout,里面什么控件都没有,因此界面上应该不会显示任何东西。

    那么接下来我们再定义一个布局文件,给它取名为button_layout.xml,代码如下所示:

    <Button xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        android:text="Button" >  
    </Button>

    
    

    这个布局文件也非常简单,只有一个Button按钮而已。现在我们要想办法,如何通过LayoutInflater来将button_layout这个布局添加到主布局文件的LinearLayout中。修改MainActivity中的代码,如下所示:

    public class MainActivity extends Activity {  
        private LinearLayout mainLayout;  
        @Override  
        protected void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
            mainLayout = (LinearLayout) findViewById(R.id.main_layout);  
            LayoutInflater layoutInflater = LayoutInflater.from(this);  
            View buttonLayout = layoutInflater.inflate(R.layout.button_layout, null);  
            mainLayout.addView(buttonLayout);  
        }  
    }  

    可以看到,这里先是获取到了LayoutInflater的实例,然后调用它的inflate()方法来加载button_layout这个布局,最后调用LinearLayout的addView()方法将它添加到LinearLayout中。

    现在可以运行一下程序,结果如下图所示:

    好了,现在对LayoutInflater的工作原理和流程也搞清楚了,你该满足了吧。额。。。。还嫌这个例子中的按钮看起来有点小,想要调大一些?那简单的呀,修改button_layout.xml中的代码,如下所示:

    <pre name="code" class="html"><Button xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="300dp"  
        android:layout_height="80dp"  
        android:text="Button" >  
    </Button>  

    
    

    这里我们将按钮的宽度改成300dp,高度改成80dp,这样够大了吧?现在重新运行一下程序来观察效果。咦?怎么按钮还是原来的大小,没有任何变化!是不是按钮仍然不够大,再改大一点呢?还是没有用!

    其实这里不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。

    再来看一下我们的button_layout.xml吧,很明显Button这个控件目前不存在于任何布局当中,所以layout_width和layout_height这两个属性理所当然没有任何作用。那么怎样修改才能让按钮的大小改变呢?解决方法其实有很多种,最简单的方式就是在Button的外面再嵌套一层布局,如下所示:

    <pre name="code" class="html"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
        <Button  
            android:layout_width="300dp"  
            android:layout_height="80dp"  
            android:text="Button" >  
        </Button>  
    </RelativeLayout>
    
    

    可以看到,这里我们又加入了一个RelativeLayout,此时的Button存在与RelativeLayout之中,layout_width和layout_height属性也就有作用了。当然,处于最外层的RelativeLayout,它的layout_width和layout_height则会失去作用。现在重新运行一下程序,结果如下图所示:

    上面这些内容完全摘自http://blog.csdn.net/guolin_blog/article/details/12921889

    其实这里不管你将Button的layout_width和layout_height的值修改成多少,都不会有任何效果的,因为这两个值现在已经完全失去了作用。平时我们经常使用layout_width和layout_height来设置View的大小,并且一直都能正常工作,就好像这两个属性确实是用于设置View的大小的。而实际上则不然,它们其实是用于设置View在布局中的大小的,也就是说,首先View必须存在于一个布局中,之后如果将layout_width设置成match_parent表示让View的宽度填充满布局,如果设置成wrap_content表示让View的宽度刚好可以包含其内容,如果设置成具体的数值则View的宽度会变成相应的数值。这也是为什么这两个属性叫作layout_width和layout_height,而不是width和height。

    以上解释不能很好地解决心中的疑惑,代码是最好的老师,于是:

    public void addView(View child) {
            addView(child, -1);
        }
    public void addView(View child, int index) {
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = generateDefaultLayoutParams();
                if (params == null) {
                    throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
                }
            }
            addView(child, index, params);
    }
       protected LayoutParams generateDefaultLayoutParams() {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        }

    默认调用View的getLayoutParams,如果为null会调用generateDefaultLayoutParams其实是生成WRAP_CONTENT的宽高参数

    // getLayoutParams
    public ViewGroup.LayoutParams getLayoutParams() {
            return mLayoutParams;
    }

    mLayoutParams是在哪里赋值的呢?

    public void setLayoutParams(ViewGroup.LayoutParams params) {
            if (params == null) {
                throw new NullPointerException("Layout parameters cannot be null");
            }
            mLayoutParams = params;
            resolveLayoutParams();
            if (mParent instanceof ViewGroup) {
                ((ViewGroup) mParent).onSetLayoutParams(this, params);
            }
            requestLayout();
        }

    那么是在哪里调用这个setLayoutParams函数的呢?再来看LayoutInflater类的inflate方法,发方法是将xml填充成View

    public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
    synchronized (mConstructorArgs) {  
    //根据xml设置生成对应的参数
            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 {  
    				//根据Tag生成View,同时传递给该View的参数
                    View temp = createViewFromTag(name, attrs);  
                    ViewGroup.LayoutParams params = null;  
                    if (root != null) {  
    //root不null时才执行
    //1,根据attr生成params
    //2,attachToRoot为false时意味着不将该view添加到父View,那么设置该View的参数
                        params = root.generateLayoutParams(attrs);  
                        if (!attachToRoot) {  
                            temp.setLayoutParams(params);  
                        }  
                    }  
                    rInflate(parser, temp, attrs);  
    // root不null时,并且设置将该View添加到当前的父View
                    if (root != null && attachToRoot) {  
                        root.addView(temp, params);  
                    }  
    // root为null时,或者设置将该View不添加添加到当前的父View
                    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;  
        }  
    }  

    因此可以看出:root不为null,并且添加到父View时,是直接调用root.addView(temp, params);添加View的

    指定了父View时,如果不添加到父View则会设置该View的布局参数temp.setLayoutParams(params);--------这种情况少见。

    未指定父View时,什么也不会设置。

    回头看上面的问题。在填充Button时,什么参数也没有设置。所以在调用addView的时候,child.getLayoutParams()为null,会执行到generateDefaultLayoutParams,那么设置的宽高参数根本没起作用。因此添加控件的时候最好带着布局参数添加。

    public void addView(View child, int index) {
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                params = generateDefaultLayoutParams();
                if (params == null) {
                    throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
                }
            }
            addView(child, index, params);
    }
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" >  
        <Button  
            android:layout_width="300dp"  
            android:layout_height="80dp"  
            android:text="Button" >  
        </Button>  
      
    </RelativeLayout>

    那么添加一个外部布局的原因是什么呢?其实这时将该布局addView进去时,测量该布局的宽高,父传过来的是Exactly和屏幕的size。RelativeLayout是match_parent,因此,RelativeLayout在测量的时候默认情况下是Exactly和屏幕的size,Button在测量的时候传递的是Exactly和300,80的尺寸。RelativeLayout测量完之后,肯定会设置宽高,LinearLayout里面是做了类似于下面的重新设定。

    <RelativeLayout是match_parent,因此,RelativeLayout在测量的时候默认情况下是Exactly和屏幕的size>这句错误,RelativeLayout在add的时候,由于不会设置View的参数,最终肯定会调用generateDefaultLayoutParams,默认是包裹内容wrap_content。后面:RelativeLayout在测量的时候默认情况下是AT_MOST和屏幕的size。由于是AT_MOST,测量完Button后会重新设定自身的宽高。

    mTotalLength = Math.max(totalLength,totalLength + childHeight + lp.topMargin +
                           lp.bottomMargin +getNextLocationOffset(child));</span>

    一切搞定!

     

    然而真正做的时候问题又出现了:发现activity_main会报错,因为没有指定方向,于是就指定成VERTICAL。默认是HORIZONTAL,于是…



    真是见鬼了,同样的代码,理论上分析,View buttonLayout = layoutInflater.inflate(R.layout.r_button, null);这句不会设置View的参数,最终肯定会调用generateDefaultLayoutParams,那么肯定是包裹内容啊!那为什么实际结果却是match_parent的效果?百地不得其解…开始怀疑是版本变了,默认的设置也变了,但是查了好久不是这个原因。

    把LinearLayout换成RelativeLayout竟然好了,然后去看了RelativeLayout源码的generateDefaultLayoutParams方法,确实是包裹内容wrap_content。才想起来,应该查看LinearLayout的generateDefaultLayoutParams而不是查看ViewGroup的generateDefaultLayoutParams方法

    protected LayoutParams generateDefaultLayoutParams() {
            if (mOrientation == HORIZONTAL) {
                return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
            } else if (mOrientation == VERTICAL) {
                return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            }
            return null;
    }

    这下终于大功告成!!


    重点:

    1、xml布局中最外层的layout参数一般都不起作用!!!!除非在加载布局的时候指定他的父布局!!!!

    2、不指定父布局的情况下LayoutParams为null,在addView的时候判断为null会生成默认的布局参数,一般为包裹内容。

    3、在加载布局的时候如果指定了父布局,最外层的match_parent和wrap_content不是随意写的。

    如:main_activity是match_parent的,需要在上面add几个布局。加载一个LinearLayout的布局,里面包含了一个wrap_content的ImageView,通过LayoutInflater加载:

    如果指定了父布局,外层的布局参数就起作用了,那么add这个布局到main_activity后:LinearLayout会占满整个屏幕,ImageView在LinearLayout里面是包裹内容,这是在往main_activity里面add布局就看不见了,因为屏幕已经被占完了。

    如果没有指定父布局,外层的参数倒是无所谓。根据2,设置成默认参数为包裹内容,最终add这个布局到main_activity后:LinearLayout不会占满整个屏幕,只是包裹里面控件就行了,不贪心。ImageView在LinearLayout里面是包裹内容

    指定了父布局,外层的布局参数就起作用了,因此不要乱写,指定父布局,并且写成wrap_content,最终add这个布局到main_activity后:LinearLayout不会占满整个屏幕,只是包裹里面控件,ImageView在LinearLayout里面是包裹内容,和上面情况一样。





  • 相关阅读:
    c# 获取网络流量
    【转】C#中如何实现左截取和右截取字符串
    dropdownlist同时绑定数据库和自定义内容
    菜鸟级别的WCF入门学习
    gridview里日期显示格式
    ValidationSummary控件不弹出错误提示框
    WebForms UnobtrusiveValidationMode 需要“jquery”ScriptResourceMapping。请添加一个名为 jquery (区分大小写)的 ScriptResourceMapping。
    datagridview控件去除页码
    时间大小比较
    时间为23:59:59
  • 原文地址:https://www.cnblogs.com/qhyuan1992/p/6071979.html
Copyright © 2011-2022 走看看