平时开发时,系统提供的几个布局基本就能满足我们的需求了。如果系统提供的布局无法满足需求,我们可以扩展ViewGroup类来实现自定义布局控件。先看下ViewGroup的继承图
由上图可知,ViewGroup继承自View,但他又是一个管理view的容器,我们在写布局xml的时候,会告诉容器需要设置的属性(凡是以layout为开头的属性,都是用于告诉容器的,例如layout_width),viewGroup的作用就是给出childView建议的宽和高、测量模式、childView的位置等。之所以是建议的宽和高,是因为childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高。
View的职责是根据测量模式和ViewGroup给出的建议的宽和高,计算出自己的宽和高(onMeasure中完成),同时还有个更重要的职责——在ViewGroup为其指定的区域内绘制自己的形态(onDraw中完成)。
当在LinearLayout中写childView的时候,可以写layout_gravity,layout_weight属性;在 RelativeLayout中的childView却没有 layout_gravity,layout_weight,原因就在于每个ViewGroup需要指定一个LayoutParams,用于确定childView支持哪些属性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看 LinearLayout的源码,会发现其内部定义了LinearLayout.LayoutParams,在此类中,你可以发现weight和 gravity的身影
扩展ViewGroup类需要实现的方法:
onMeasure——计算控件极其子控件所占的区域
onLayout——布局子控件
dispatchDraw——绘制布局控件
super.onMeasure()调用后将可以得到控件的宽高。
控件的onMeasure(),onLayout()方法会被执行多次,注意这里面执行的逻辑不要被多次执行了。
要绘制控件,分为两步:
1.在onMeasure()方法中绘制好控件的大小
2.在onLayout方法里布局好控件的位置用view.layout(left,top,right,bottm)
在OnMeasure(int widthMeasureSpec, int heightMeasureSpec)中的两个参数widthMeasureSpec,heightMeasureSpec里包含了控件的宽高和测量模式。
测量模式有MeasureSpec.EXACTLY,MeasureSpec.ATMOST,MeasureSpec.EXACTLY,MeasureSpec.UNSPECIFIED
得出的测量模式和在布局文件里的layout-width ,layout-height设置的属性有关
当属性值为fill_parent、match_paren、具体数值时,mode为MeasureSpec.EXACTLY
当属性值为wrap_content时,mode为MeasureSpec.ATMOST
MeasureSpec.UNSPECIFIED模式很少用,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中,意思为想设置多大就多大
最后可以为空间指定大小 setMeasuredDimension();这个方法里的参数是数值,不包含测量模式
还有另外一种方法去控制控件的大小
leftlayout.measure(sideWidth, heightMeasureSpec);
这里的参数是带有测量模式的
int sideWidth=MeasureSpec.makeMeasureSpec((int)(MaxWidth*0.5),MeasureSpec.EXACTLY);
这个是制作带有测量模式的长度值
还是盗用一下鸿洋大神的例子,定义一个ViewGroup,内部可以传入0到4个childView,分别依次显示在左上角,右上角,左下角,右下角。

1、决定该ViewGroup的LayoutParams

对于这个例子,我们只需要ViewGroup能够支持margin即可,因此直接使用系统的MarginLayoutParams

@Override
    public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }
重写父类的该方法,返回MarginLayoutParams的实例,这样就为我们的ViewGroup指定了其LayoutParams为MarginLayoutParams。

2、onMeasure

在onMeasure中计算childView的测量值以及模式,以及设置自己的宽和高:
/**
     * 计算所有ChildView的宽度和高度,然后根据ChildView的计算结果,设置自己的宽和高
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        /**
         * 获得此ViewGroup上级容器为其设置的宽和高,以及测量模式
         */
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
        int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
        // 通过ViewGroup的measureChildren方法为其所有的childrenView设置宽和高
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        /**
         * 记录如果是wrap_content时,设置的宽和高
         */
        int width = 0;
        int height = 0;
        int cCount = getChildCount();
        int cWidth = 0;
        int cHeight = 0;
        MarginLayoutParams cParams = null;
        // 用于计算左边两个childView的高度
        int lHeight = 0;
        // 用于计算右边两个childView的高度,最终高度取二者之间大值
        int rHeight = 0;
        // 用于计算上边两个childView以及设置的margin的所确定的宽度
        int tWidth = 0;
        // 用于计算下面两个childiew以及设置的margin的所确定的宽度,最终宽度取二者之间大值
        int bWidth = 0;
        /**
         * 根据childView计算得出的宽和高,以及设置的margin计算容器的宽和高,主要用于容器是warp_content时
         */
        for (int i = 0; i < cCount; i++) {
            View childView = getChildAt(i);
            cWidth = childView.getMeasuredWidth();
            cHeight = childView.getMeasuredHeight();
            cParams = (MarginLayoutParams) childView.getLayoutParams();
            // 上面两个childView确定的宽度
            if (i == 0 || i == 1) {
                tWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
            }
            // 下面两个childView确定的宽度
            if (i == 2 || i == 3) {
                bWidth += cWidth + cParams.leftMargin + cParams.rightMargin;
            }
            // 左边两个childView确定的高度
            if (i == 0 || i == 2) {
                lHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
            }
            // 右边两个childView确定的高度
            if (i == 1 || i == 3) {
                rHeight += cHeight + cParams.topMargin + cParams.bottomMargin;
            }
        }
        // 取最大值
        width = Math.max(tWidth, bWidth);
        height = Math.max(lHeight, rHeight);
        /**
         * 如果是精确值或match_parent,设置为父容器计算的值,否则设置为我们计算的值
         * 
         */
        setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? sizeWidth
                : width, (heightMode == MeasureSpec.EXACTLY) ? sizeHeight
                : height);
    }

在获取该ViewGroup父容器为其设置的计算模式和尺寸时,通常情况下,只要不是wrap_content,父容器都能正确的计算其尺寸。所以如果设置为wrap_content时,就需要通过其childView的宽和高来进行计算。

3、使用onLayout对其所有childView设置绘制区域


protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
        // TODO Auto-generated method stub
        int cCount = getChildCount();
        int cWidth = 0; //childrenView的宽度
        int cHeight = 0;//childrenView的高度
        MarginLayoutParams cParams = null;
        /**
         * 遍历所有childView根据其宽和高,以及margin进行布局
         */
        for (int i = 0; i < cCount; i++) {
            View childView = getChildAt(i);
            cWidth = childView.getMeasuredWidth();
            cHeight = childView.getMeasuredHeight();
            cParams = (MarginLayoutParams) childView.getLayoutParams();
         

int cl = 0;//左边位置
            int ct = 0;//顶部位置
            int cr = 0;//右边位置
            int cb = 0;//底部位置

            switch (i) {
            case 0:
                cl = cParams.leftMargin;
                ct = cParams.topMargin;
                break;
            case 1:
                cl = getWidth() - cWidth - cParams.leftMargin
                        - cParams.rightMargin;
                ct = cParams.topMargin;
                break;
            case 2:
                cl = cParams.leftMargin;
                ct = getHeight() - cHeight - cParams.bottomMargin;
                break;
            case 3:
                cl = getWidth() - cWidth - cParams.leftMargin
                        - cParams.rightMargin;
                ct = getHeight() - cHeight - cParams.bottomMargin;
                break;
            }
            cr = cl + cWidth;
            cb = cHeight + ct;
            // 按左上右下的顺序给出边距,确定childView布局范围
            childView.layout(cl, ct, cr, cb);
        }
    }

4、使用方案

布局1:
<com.example.view.CustomViewGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#AA333333" >
        <TextView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#FF0000"
            android:gravity="center"
            android:text="0"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#00ff00"
            android:gravity="center"
            android:text="1"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#0000FF"
            android:gravity="center"
            android:text="2"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#00FFff"
            android:gravity="center"
            android:text="3"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
    </com.example.view.CustomViewGroup>

效果:

布局2:

 <com.example.view.CustomViewGroup
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:background="#AA333333" >
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#FF0000"
            android:gravity="center"
            android:text="0"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#00ff00"
            android:gravity="center"
            android:text="1"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#0000FF"
            android:gravity="center"
            android:text="2"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#00FFff"
            android:gravity="center"
            android:text="3"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
    </com.example.view.CustomViewGroup>

效果:

布局3:
 <com.example.view.CustomViewGroup
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#AA333333" >
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#FF0000"
            android:gravity="center"
            android:text="0"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#00ff00"
            android:gravity="center"
            android:text="1"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#0000FF"
            android:gravity="center"
            android:text="2"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#00FFff"
            android:gravity="center"
            android:text="3"
            android:textColor="#FFFFFF"
            android:textSize="22sp"
            android:textStyle="bold" />
    </com.example.view.CustomViewGroup>

效果:

现在无论ViewGroup的宽和高的值如何定义,我们都实现了预期的效果,其实根据这个例子,我们完全可以写一个自己的LinearLayout,学习最重要的就是融汇贯通,大家研究下吧

源代码

参考:

http://blog.csdn.net/lmj623565791/article/details/38339817

ViewGroup1——自定义布局相关推荐

  1. UICollectionView自定义布局(二)

    这是UICollectionView自定义布局的第二篇,实现类似UltravisualApp的视差效果,同样这篇文章的教程来自Ray家的Swift Expanding Cells in iOS Col ...

  2. iOS开发学无止境 - UICollectionView自定义布局之风火轮[译]

    现在有许多极具创造力的网站,几周前我碰巧浏览到一个名为Form Follows Function的网站,上面有各种交互动画.其中最吸引我的是网站上的导航转轮,转轮由各种交互体验海报组成. 原文:UIC ...

  3. 从自定义TagLayout看自定义布局的一般步骤[手动加精]

    从自定义TagLayout看自定义布局的一般步骤[手动加精] 我们常用的布局有LinearLayout,FrameLayout,RelativeLayout,大多数情况下都能满足我们的需求,但是也有很 ...

  4. Swift - 使用网格(UICollectionView)的自定义布局实现复杂页面

    网格UICollectionView除了使用流布局,还可以使用自定义布局.实现自定义布局需要继承UICollectionViewLayout,同时还要重载下面的三个方法: 1 2 3 4 5 6 7 ...

  5. 【Android 性能优化】布局渲染优化 ( 过渡绘制 | 背景设置产生的过度绘制 | Android 系统的渲染优化 | 自定义布局渲染优化 )

    文章目录 一. 背景设置产生的过度绘制 二. Android 系统的渲染优化 1. 透明组件数据传递 2. GPU 存储机制 3. Android 7.0 之后的优化机制 三. 自定义布局渲染优化 一 ...

  6. android 前台服务自定义布局不显示_Android自定义LinearLayout布局显示不完整的解决方法...

    发现问题 原需求,在一个伸缩列表中,自定义LinearLayout继承LinearLayout动态添加布局. 然而实现的时候:一共遍历了30条数据,却只显示了一条 断点查看代码:遍历addView() ...

  7. 【教程】如何在标签打印工具TFORMer Designer中自定义布局?

    TEC-IT的在线标签生成器TFORMer Designer提供标签打印服务,并提供即用型行业标签模板作为Web服务.使用此软件,您可以在几秒钟内创建您自己的标签和表格或在工业和物流业中使用即时可用的 ...

  8. re管理器Java_自定义布局管理器-FormLayout

    第二部分:自定义布局管理器 在java.awt包与javax.swing包下有许多现成的布局类,比如BorderLayout.FlowLayout,还有较为复杂的.用于精确定位的布局类GridBagL ...

  9. android dialog 自定义布局,如何设置AlertDialog的自定义布局?

    调用我的对话框:alertDialog = showInfoDialog(message = "$wrongPasscodeMessage\n$retryMessage") 方法如 ...

最新文章

  1. go map的定义和使用 键值对存储
  2. 【教程】Linux 系统下对目录扩容的方法
  3. crtlc不能复制文件_ctrl+c不能复制怎么办
  4. adobe字体_Adobe发布全新LOGO!字体颜色变红了
  5. Spring boot 默认静态资源路径与手动配置访问路径的方法
  6. BZOJ_1606_ [Usaco2008_Dec]_Hay_For_Sale _购买干草_(背包)
  7. WinForm 对EXCEL 的操作(三)
  8. 【Oracle】手工建库时启动到nomount状态时错误ORA-09925,ORA-01017
  9. .NET的垃圾回收机制引发的问题
  10. 工具武装的前端开发工程师
  11. NOIP2015酱油记
  12. Cent OS7基础 第五节
  13. AndroidStudio|读取SD卡中的sqlite数据
  14. 永恒之蓝 MS17-010漏洞复现
  15. serv-u ftp server是什么?如何利用花生壳搭建ftp服务器?
  16. 用JS控制flvplayer.swf播放视频
  17. python画名侦探柯南_机器学习机器学习三剑客之Matplotlab
  18. [GDOI2016][树链剖分+主席树]疯狂动物城
  19. LINUX学习基础篇(三十三)系统资源
  20. Win32 双人五子棋

热门文章

  1. MariaDB/MySQL从数据库中选择随机的行
  2. anaconda下安装ffmpeg
  3. linux 编写系统服务,Linux shell编写系统服务脚本
  4. 宝塔mysql优化_宝塔面板下实现MySQL性能优化处理
  5. 电路非门_【连载】电路和维修基础之门电路、转换器
  6. Rabbit-用户上线接收消息
  7. 取java.sql.date日期_JAVA 处理时间 - java.sql.Date、java.util.Date与数据库中的Date字段的转换方法[转]...
  8. 11、Kubernetes集群安全机制
  9. 罗伯特扫地机器人电池如何取_irobot扫地机器人电池怎么拆 iRobot/艾罗伯特扫地...
  10. python爬虫源码怎么使用_Python爬虫具体应该怎么使用?