• 安卓冷知识:LayoutParams


    安卓的布局有很多种,每种都有对应的LayoutParams类,那么它们之间到底是什么关系?

    为什么在编写Layout的XML文件时,有的layout_前缀属性有用有的没有用?

    一句话道出LayoutParams的本质:LayoutParams是Layout提供给其中的Children使用的。我们来看一段不用XML文件创建布局的代码。

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            LinearLayout ll = new LinearLayout(this);
            ll.setOrientation(LinearLayout.VERTICAL);
    
            TextView tv = new TextView(this);
            tv.setText("I am a text view, haha");
            tv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
            ll.addView(tv);
    
            setContentView(ll);
        }

    可见LayoutParams的对象是设置在Layout中的Child View上的,而不是设置在自己上。就是说View想要放到某种Layout中,就必须设置这个Layout规定的LayoutParams。

    每一种Layout实现不同的布局方式都有其独特的参数设置,例如LinearLayout.LayoutParams的weight属性,用来设置Child View之间的大小比例,如果把这个设置给FrameLayout是没有意义的,因为FrameLayout并不会排列Child View而是会都“挤在一起”。所以在XML文件里设置View的属性的时候,有的layout属性是无效的,也是没有意义的。

    如果仔细看上面那段代码,会发现有个问题,设置TextView的LayoutParams是ViewGroup.LayoutParams,而不是LinearLayout.LayoutParams。查看源代码可以看到LinearLayout.LayoutParams是ViewGroup.LayoutParams的子类,那么会不会产生运行时错误?下面通过分析源代码找到这个问题的答案。

    使Layout与View发生关系的语句是LinearLayout.addView(),先找到LinearLayout.java(frameworks/base/core/java/android/widget/LinearLayout.java)发现没有override这个方法,到父类ViewGroup(frameworks/base/core/java/android/view/ViewGroup.java)中寻找addView。

        public void addView(View child) {
            addView(child, -1);
        }
        public void addView(View child, int index) {
            LayoutParams params = child.getLayoutParams();
            if (params == null) {
                // ......
            }
            addView(child, index, params);
        }
        public void addView(View child, int index, LayoutParams params) {
            requestLayout();
            invalidate(true);
            addViewInner(child, index, params, false);
        }

    addView有N个重载(overload)的版本,注意第二个是直接获取Child View的LayoutParams,这里没有经过处理,最后传递给了addViewInner()方法,再来看addViewInner()方法的代码:

        private void addViewInner(View child, int index, LayoutParams params,
                boolean preventRequestLayout) {
    
            // ...
    
            if (!checkLayoutParams(params)) {
                params = generateLayoutParams(params);
            }
            
            // ...
        }

    发现里面有这样两个调用checkLayoutParams()和generateLayoutParams()可能会修改或替换传进来的params,找到这两个函数发现ViewGroup中的实现几乎是空的:

        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return  p != null;
        }
        protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
            return p;
        }

    让我们回到LinearLayout里看看有没有override这两个方法:

        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return p instanceof LinearLayout.LayoutParams;
        }
        @Override
        public LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
            return new LinearLayout.LayoutParams(p);
        }

    checkLayoutParams做的事情是检查这个LayoutParam对象是否是LinearLayout.LayoutParams的实例,按照刚开始写的代码运行到这里就返回false了,于是执行generateLayoutParams,通过ViewGroup.LAyoutParams创建一个新的LinearLayout.LayoutParams。这样就保证了使用的一定是LinearLayout.LayoutParams或者其子类。再看一下LinearLayout.LayoutParams的构造函数:

            public float weight;
            public int gravity = -1;
            public LayoutParams(ViewGroup.LayoutParams p) {
                super(p);
            }
            public LayoutParams(ViewGroup.MarginLayoutParams source) {
                super(source);
            }
            public LayoutParams(LayoutParams source) {
                super(source);
    
                this.weight = source.weight;
                this.gravity = source.gravity;
            }

    显然应该调用的是第一个构造函数,然后weight和gravity的值取默认值。为什么要定义第一个和第二个构造函数?因为LinearLayout.LayoutParams继承自ViewGroup.MarginLayoutParams,而ViewGroup.MarginLayoutParams继承自ViewGroup.LayoutParams。可见二者是LinearLayout.LayoutParams继承树上的所有祖先类。添加这两个构造方法使能通过祖先类对象构造自己,配合上面的check和generate使不适配的LayoutParams变得可用。

    根据以上分析,可以得出一个小结论,使用代码构建界面的时候,可以随意使用ViewGroup.LayoutParams或者ViewGroup.MarginLayoutParams,因为所有的Layout中的LayoutParams都继承自他们,但是也会付出一点点代价就是会新创建一个对象。不过毕竟靠代码构建界面的场景很少,一般用XML就可以了,这也算是个冷知识点吧。

  • 相关阅读:
    Raspberry Pi(树莓派2代B型新手初体验)
    Webview内存泄漏解决办法
    学习日记(二)——自定义来电界面,监听来电广播,悬浮窗
    学习日记(一)----BaseActivity的写法
    学习记录---- viewpager里嵌套listview中adapter的写法
    listView的流畅性优化
    git 命令学习(二)
    git 命令学习(一)
    软件项目管理结课小结
    软件测试结课小结
  • 原文地址:https://www.cnblogs.com/ajeyone/p/layoutparams.html
Copyright © 2020-2023  润新知