理解Android ViewGroup及自定义ViewGroup

  • 什么是父控件和子控件
  • 什么是ViewGroup
  • ViewGroup的工作原理
  • 自定义ViewGroup步骤
  • 复杂的自定义ViewGroup

什么是父控件和子控件

父控件就是容纳子控件的控件(也就是我们常说的布局)也称作容器,常见的父控件有LinearLayout,RelativeLayout,FrameLayout,TableLayout,GridLayout;
        子控件是被父控件(也就是我们常说的布局)包裹住的控件,常见的子控件有TextView,Button,Edit Text,ImageView
例:下面的代码中LinearLayout为父控件,里面容纳的TextView为子控件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><TextView android:layout_width="match_parent"android:layout_height="wrap_content"android:text="这是一段文字"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/>
</LinearLayout>

什么是ViewGroup

ViewGroup是上面提到的所有的父控件的父类;但ViewGroup是一个抽象类,它里面有一个抽象方法onLayout,这个方法的作用就是摆放它所有的子控件(安排位置),因为是抽象类,不能直接new对象,所以我们在布局文件中不能直接使用 ViewGroup
例:直接使用ViewGroup为父控件运行后会报错

<?xml version="1.0" encoding="utf-8"?>
<ViewGroupxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="wrap_content"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="这是一段文字"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/>
</ViewGroup>

                                                                                               不能实例化抽象类android.view.ViewGroup

ViewGroup的工作原理

创建一个View(执行构造方法)的时候不需要测量控件的大小,只有将这个view放入容器(父控件)中的时候才需要测量;当子控件的父控件要放置该子控件的时候,父控件会调用子控件的onMeaonsure()方法,然后传入两个参数widthMeasureSpec和heightMeasureSpec,这两个参数就是父控件告诉子控件可获得的空间以及关于这个空间的约束条件,子控件取得这些条件就能正确的测量自身的宽高了
如果还有不明白请点击这里onMeaonsure()方法详解

自定义ViewGroup步骤

  1. 覆盖构造方法(必须)
  2. 重写onMeaonsure()测量控件尺寸(可选)
  3. 重写onLayout()方法摆放控件(必须)

下面是个简单的自定义ViewGroup的例子,顺序放置所有控件,如果一行不够放置则换行

public class MyLayout extends ViewGroup {private Context mContext;public MyLayout(Context context) {super(context, null);}public MyLayout(Context context, AttributeSet attrs) {super(context, attrs, 0);this.mContext=context;}public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {measureChildren(widthMeasureSpec, heightMeasureSpec);// 计算出所有的childView的宽和高//测量并保存layout的宽高(使用getDefaultSize时,wrap_content和match_perent都是填充屏幕)//稍后会重新写这个方法,能达到wrap_content的效果setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {final int count = getChildCount();   //子控件个数int childWidth = 0;                  //子控件宽度int childHeight = 0;                 //子控件高度int myLayoutWidth = 0;               //MyLayout当前宽度int myLayoutHeight = 0;              //MyLayout当前高度int maxHeight = 0;                   //一行中子控件最高的高度for(int i = 0; i<count; i++){View child = getChildAt(i);//注意此处不能使用getWidth和getHeight,这两个方法必须在onLayout执行完,才能正确获取宽高childWidth = child.getMeasuredWidth();       //子控件的测量宽度childHeight = child.getMeasuredHeight();     //子控件的测量高度if( getWindowWidthPixels ()-myLayoutWidth > childWidth ){ //屏幕剩余宽度大于子控件宽度left = myLayoutWidth;right = left+childWidth;top = myLayoutHeight;bottom = top+childHeight;} else{ //屏幕剩余宽度小于子控件宽度就换行myLayoutWidth = 0;            //myLayoutWidth重置为0myLayoutHeight += maxHeight;  //当前myLayoutHeight=myLayoutHeight+上一行子控件的最大高maxHeight = 0;                //maxHeight重置为0left = myLayoutWidth;right = left+childWidth;top = myLayoutHeight;bottom = top+childHeight;}myLayoutWidth += childWidth;  //宽度累加if(childHeight > maxHeight){  //如果子控件的高度大于行最大高度maxHeight = childHeight;  //行最大高度设置为子控件高度}child.layout(left, top, right, bottom);//摆放子控件(左上右下)点的坐标值}}//获取屏幕宽度private int getWindowWidthPixels () {Resources resources = mContext.getResources();DisplayMetrics displayMetrics = resources.getDisplayMetrics();int width = displayMetrics.widthPixels;return width;}
}
<com.example.andy.mytest.MyLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:id="@+id/textView1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView1"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView2"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView3"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView3"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView4"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView4"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView5"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView5"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView6"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView6"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView7"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView7"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/><TextViewandroid:id="@+id/textView8"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="TextView8"android:textSize="25dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"/>
</com.example.andy.mytest.MyLayout>

复杂的自定义ViewGroup

例:编写一个自定义ViewGroup扩展它的布局属性,子控件按扩展的布局属性放置显示

  1. 自定义ViewGroup的布局属性
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="MyTextView"><!--声明MyTextView需要使用系统定义过的text属性,注意前面需要加上android命名--><attr name="android:text" /><attr name="android:layout_width" /><attr name="android:layout_height" /><attr name="android:background" /><attr name="mTextColor" format="color" /><attr name="mTextSize" format="dimension" /></declare-styleable><declare-styleable name ="CustomLayout"><attr name ="layout_position"><enum name ="center" value="0" /><enum name = "Left" value="1"/><enum name = "Right" value="2"/><enum name = "top" value="3"/><enum name = "Bottom" value="4"/><enum name ="LeftTop" value="5" /><enum name ="RightTop" value="6" /><enum name ="LeftBottom" value="7" /><enum name ="RightBottom" value="8" /></attr ></declare-styleable>
</resources>
  1. 自定义LayoutParams类
    自定义LayoutParams的作用是扩展ViewGroup的布局属性
           可以继承ViewGroup.LayoutParams,也可以继承ViewGroup.MarginLayoutParams;
           继承ViewGroup.LayoutParams,自定义布局只是简单的支持layout_width和layout_height属性;
           继承ViewGroup.MarginLayoutParams,就能使用layout_marginxxx属性
public class MyLayoutParams extends ViewGroup.MarginLayoutParams {public static final int POSITION_CENTER = 0;        // 中间public static final int POSITION_LEFT = 1;          // 左侧public static final int POSITION_RIGHT = 2;         // 右侧public static final int POSITION_TOP = 3;           // 上侧public static final int POSITION_BOTTOM = 4;        // 下侧public static final int POSITION_LEFT_TOP = 5;      // 左上方public static final int POSITION_RIGHT_TOP = 6;     // 右上方public static final int POSITION_LEFT_BOTTOM = 7;   // 左下方public static final int POSITION_RIGHT_BOTTOM = 8;  // 右下方public int POSITION = POSITION_CENTER;              // 默认位置为中间//在这个构造方法中初始化参数值,会使布局文件被映射为对象的时候被调用public MyLayoutParams(Context c, AttributeSet attrs) {super(c, attrs);TypedArray typedArray = c.obtainStyledAttributes(attrs,R.styleable.CustomLayout );//获取设置在子控件上的位置属性POSITION = typedArray.getInt(R.styleable.CustomLayout_layout_position ,POSITION );typedArray.recycle();}public MyLayoutParams(int width, int height) {super(width, height);}public MyLayoutParams(ViewGroup.MarginLayoutParams source) {super(source);}public MyLayoutParams(ViewGroup.LayoutParams source) {super(source);}
}
  1. 重写generateLayoutParams()
<com.example.andy.mytest.MyLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:ying="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/holo_red_light"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="文字1"android:textSize="25dp"android:padding="10dp"android:layout_marginLeft="10dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"ying:layout_position="LeftTop"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="文字2"android:textSize="25dp"android:padding="10dp"android:layout_marginLeft="10dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"ying:layout_position="LeftBottom"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="文字3"android:textSize="25dp"android:padding="10dp"android:layout_marginRight="10dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"ying:layout_position="RightTop"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="文字4"android:textSize="25dp"android:padding="10dp"android:layout_marginRight="10dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"ying:layout_position="RightBottom"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="文字5"android:textSize="25dp"android:padding="10dp"android:layout_marginTop="50dp"android:textColor="@android:color/white"android:background="@android:color/holo_blue_light"ying:layout_position="center"/>
</com.example.andy.mytest.MyLayout>
public class MyLayout extends ViewGroup {private Context mContext;public MyLayout(Context context) {super(context, null);}public MyLayout(Context context, AttributeSet attrs) {super(context, attrs, 0);this.mContext=context;}public MyLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//父控件ViewGroupMode和Sizeint widthMode = MeasureSpec. getMode(widthMeasureSpec);int heightMode = MeasureSpec. getMode(heightMeasureSpec);int widthSize = MeasureSpec. getSize(widthMeasureSpec);int heightSize = MeasureSpec. getSize(heightMeasureSpec);int layoutWidth = 0;int layoutHeight = 0;int measureWidth = 0;int measureHeight = 0;int count = getChildCount();//遍历所有子控件并保存了宽和高for( int i = 0; i < count; i++){View child = getChildAt(i);measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);}MyLayoutParams params = null;if(widthMode == MeasureSpec. EXACTLY){ //父控件ViewGroup宽度设置(size/match_parent)layoutWidth = widthSize;           //layoutWidth=父控件剩余的宽度} else{ //父控件ViewGroup宽度设置(UNSPECIFIED/wrap_content)for ( int i = 0; i < count; i++)  { //layoutWidth=遍历子控件后,所有子控件宽度最大的View child = getChildAt(i);//子控件宽度值//不管子控件宽度设置是match_parent或wrap_content;则宽度值为子控件宽度+左右padding值//若高度设置是具体高度值;则宽度值为设置的具体宽度值,不包含左右padding值measureWidth = child.getMeasuredWidth(); //子控件宽度值(若设置了padding则包含左右padding值)params = (MyLayoutParams) child.getLayoutParams();//获取子控件宽度和左右边距之和,作为这个控件需要占据的宽度int marginWidth = measureWidth + params.leftMargin + params.rightMargin ;layoutWidth = marginWidth > layoutWidth ? marginWidth : layoutWidth;}}if(heightMode == MeasureSpec. EXACTLY){ //父控件ViewGroup高度设置(size/match_parent)layoutHeight = heightSize;          //layoutHeight=父控件剩余的高度} else{ //父控件ViewGroup高度设置(UNSPECIFIED/wrap_content)for ( int i = 0; i < count; i++)  {View child = getChildAt(i);//子控件高度值//不管子控件高度设置是match_parent或wrap_content;则高度值为子控件高度+上下padding值//若高度设置是具体高度值;高度值为设置的具体高度值,不包含上下padding值measureHeight = child.getMeasuredHeight();params = (MyLayoutParams) child.getLayoutParams();//获取子控件高度和上下边距之和,作为这个控件需要占据的高度int marginHeight = measureHeight + params.topMargin + params.bottomMargin ;layoutHeight = marginHeight > layoutHeight ? marginHeight : layoutHeight;}}// 测量并保存父控件ViewGroup的宽高setMeasuredDimension(layoutWidth, layoutHeight);}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {final int count = getChildCount();int childMeasureWidth = 0;int childMeasureHeight = 0;MyLayoutParams params = null;for ( int i = 0; i < count; i++) {View child = getChildAt(i);// 注意此处不能使用getWidth和getHeight,这两个方法必须在onLayout执行完,才能正确获取宽高childMeasureWidth = child.getMeasuredWidth();childMeasureHeight = child.getMeasuredHeight();params = (MyLayoutParams) child.getLayoutParams();switch (params. POSITION) {case MyLayoutParams. POSITION_CENTER:    // 中间//getWidth()为父布局宽度left = (getWidth()-childMeasureWidth)/2 - params.rightMargin + params.leftMargin ;top = (getHeight()-childMeasureHeight)/2 + params.topMargin - params.bottomMargin ;break;case MyLayoutParams. POSITION_LEFT_TOP:      // 左上方left = 0 + params. leftMargin;top = 0 + params. topMargin;break;case MyLayoutParams. POSITION_RIGHT_TOP:     // 右上方left = getWidth() - params.rightMargin - childMeasureWidth;top = 0 + params. topMargin;break;case MyLayoutParams. POSITION_LEFT_BOTTOM:    // 左下角left = 0 + params. leftMargin;top = getHeight() - params.bottomMargin - childMeasureHeight;break;case MyLayoutParams. POSITION_RIGHT_BOTTOM:// 右下角left = getWidth() - params.rightMargin - childMeasureWidth;top = getHeight() - params.bottomMargin -childMeasureHeight;break;default:break;}// 确定子控件的位置,四个参数分别代表(左上右下)点的坐标值child.layout(left, top, left+childMeasureWidth, top+childMeasureHeight);}}//在布局文件被填充为对象的时候调用//如果不重写布局文件中设置的布局参数无法取得@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {return new MyLayoutParams(getContext(), attrs);}@Overrideprotected LayoutParams generateLayoutParams(LayoutParams p) {return new MyLayoutParams(p);}@Overrideprotected LayoutParams generateDefaultLayoutParams() {return new MyLayoutParams(LayoutParams.MATCH_PARENT , LayoutParams.MATCH_PARENT);}@Overrideprotected boolean checkLayoutParams(LayoutParams p) {return p instanceof MyLayoutParams ;}}

ViewGroup详解相关推荐

  1. Android自定义ViewGroup的OnMeasure和onLayout详解

    前一篇文章主要讲了自定义View为什么要重载onMeasure()方法http://blog.csdn.net/tuke_tuke/article/details/73302595 那么,自定义Vie ...

  2. android jar 包 意见反馈功能,android重点jar包详解.docx

    android重点jar包详解 深入理解View(一):从setContentView谈起 我们都知道?MVC,在Android中,这个?V?即指View,那我们今天就来探探View的究竟.在onCr ...

  3. LayoutInflater的inflate函数用法详解

    LayoutInflater的inflate函数用法详解 LayoutInflater作用是将layout的xml布局文件实例化为View类对象. 获取LayoutInflater的方法有如下三种: ...

  4. [Android] DiffUtil在RecyclerView中的使用详解

    概述 DiffUtil是recyclerview support library v7 24.2.0版本中新增的类,根据Google官方文档的介绍,DiffUtil的作用是比较两个数据列表并能计算出一 ...

  5. Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析

    转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...

  6. ViewPager 详解(三)---PagerTabStrip与PagerTitleStrip添加标题栏的异同

     相关文章: 1.<ViewPager 详解(一)---基本入门> 2.<ViewPager 详解(二)---详解四大函数> 3.<ViewPager 详解(三)-- ...

  7. Android事件流程详解

    Android事件流程详解 网络上有不少博客讲述了android的事件分发机制和处理流程机制,但是看过千遍,总还是觉得有些迷迷糊糊,因此特地抽出一天事件来亲测下,向像我一样的广大入门程序员详细讲述an ...

  8. Fragment生命周期详解

    关于Fragment的生命周期,博主写过Activity与Fragment生命周期详解,基本上把Fragment的生命周期详细介绍过,但是那仅仅是创建一个Fragmnet时的生命周期,而事实上Frag ...

  9. Android LayoutInflater详解

    Android LayoutInflater详解 在实际开发中LayoutInflater这个类还是非常有用的,它的作用类 似于findViewById().不同点是LayoutInflater是用来 ...

  10. 【流媒体开发】VLC Media Player - Android 平台源码编译 与 二次开发详解 (提供详细800M下载好的编译源码及eclipse可调试播放器源码下载)

    作者 : 韩曙亮  博客地址 : http://blog.csdn.net/shulianghan/article/details/42707293 转载请注明出处 : http://blog.csd ...

最新文章

  1. CRM端 equipment hierarchy change成功的标志
  2. linux禁止系统休眠,让linux系统休眠
  3. 冒着得罪大佬的风险,曝光下这件事
  4. Happy Programming Contest
  5. from xx is not a valid DFS filename
  6. Activity详解(生命周期、以各种方式启动Activity、状态保存,完全退出等)
  7. 在MAC系统上,重新编译了一次OpenJDK8
  8. 黑马程序员 (重要)单进程、线程、非堵塞实现并发的原理
  9. C#中的线程(三)多线程
  10. 密码管理系统竞品分析报告
  11. ar9285网卡驱动 for linux,atheros ar9285无线网卡驱动 免费版
  12. 介绍几款WAP网页制作工具(提供下载)
  13. Linux里面qt的可执行文件在命令行中可以打开,双击可执行文件打不开
  14. python好用的内置库_python内置的高效好用各种库
  15. 项目过程管理(二)工具与流程
  16. 无偏估计的数学证明和分析
  17. Ubuntu20.04成功安装google浏览器,并正常使用Bing等其他搜索引擎
  18. 微信小程序实现物流步骤条
  19. 蜗牛爬井题目c语言,【3年级】40、蜗牛爬井问题
  20. 数字孪生--技术介绍

热门文章

  1. 解决Chrome 的右键谷歌网页翻译失效 20221107更新
  2. matlab凑数求和,凑数求和算法 C语言问题 C语言求和算法
  3. ERP系统如何完成工厂车间流程
  4. linux安装打字软件
  5. 计算机考研,这样选学校才是正解
  6. 网站服务器对域名有要求,网站域名备案对服务器的要求
  7. 第三章:顺序结构程序设计(练习题)
  8. 流程即代码:云研发、低代码 IDE —— Uncode
  9. typora免费将图片上传到CSDN
  10. 面转栅格之ERROR 999999:执行函数时出错