Android ViewGroup介绍+实例

举报
帅次 发表于 2021/12/23 00:33:46 2021/12/23
【摘要】 ViewGroup         ViewGroup是一个特殊的View,可以包含其他视图(称为子视图)。而ViewGroup是View的子类,所以ViewGroup可以当成普通的UI组件使用。ViewGroup是布局和视图容器的基类,该类还定义了Vie...

ViewGroup

        ViewGroup是一个特殊的View,可以包含其他视图(称为子视图)。而ViewGroup是View的子类,所以ViewGroup可以当成普通的UI组件使用。ViewGroup是布局和视图容器的基类,该类还定义了ViewGroup.LayoutParams用作布局参数基类的类。

         由于ViewGroup的直接子类和间接子类比较多,上图描述了展示了部分子类。下面把放在android.widget包下的ViewGroup的全部子类展示出来。

        继承关系该写的基本差不多了。下面咱学习一个自定义ViewGroup。

自定义 ViewGroup

ViewGroup常用重写方法

onMeasure()

        遍历自己的子View对自己的每一个子View进行measure,绝大多数时候对子View的measure都可以直接用measureChild()这个方法来替代。确定子View的宽高和自己的宽高以后 再调用setMeasuredDimension将ViewGroup自身的宽和高传给它的父View,才可以继续写onLayout()方法。

onSizeChanged()

        在onMeasure()后执行,只有大小发生了变化才会执行onSizeChange()。

onLayout()

        排列所有子View的位置,通过getChildCount()获取所有子view,getChildAt获取childview调用各自的layout(int l, int t, int r, int b)方法来排列自己。

onDraw()

        自定义ViewGroup默认不会触发onDraw方法,需要设置背景色或者setWillNotDraw(false)来手动触发。

        注意: ViewGroup的onLayout()方法是必须重写的,而onDraw()方法默认是不会调用。如果想执行onDraw方法,可以通过下面两种方法:

  • 1.设置透明背景:
    • 在构造函数中:setBackgroundColor(Color.TRANSPARENT);

    • 在xml中:android:background="@color/transparent"

  • 2.在构造函数中添加setWillNotDraw(false)不进行自行绘制View。

实例

下面咱们写一个简单的栗子,先看效果图。

1.创建CustomLayout继承ViewGroup


  
  1. /**
  2.  * 编写自定义ViewGroup的示例。
  3.  */
  4. public class CustomLayout extends ViewGroup {
  5. //    private int childHorizontalSpace = 20;
  6. //    private int childVerticalSpace = 20;
  7.     private int childHorizontalSpace;
  8.     private int childVerticalSpace;
  9.     //从代码创建视图时使用的简单构造函数。
  10.     public CustomLayout(Context context) {
  11.         super(context);
  12.     }
  13.     //从XML使用视图时调用的构造函数。
  14.     public CustomLayout(Context context, AttributeSet attrs) {
  15.         super(context, attrs);
  16.         TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.CustomLayout);
  17.         if (attrArray != null) {
  18.             childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_horizontalSpace, 12);
  19.             childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_verticalSpace, 12);
  20.             MLog.e(getClass().getName(),"HorizontalSpace:"+childHorizontalSpace+"|VerticalSpace:"+childVerticalSpace);
  21.             attrArray.recycle();
  22.         }
  23.         //此视图是否自行绘制
  24.         setWillNotDraw(false);
  25.     }
  26.     /**
  27.      * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高
  28.      */
  29.     @Override
  30.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  31.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  32.         MLog.e(getClass().getName(),"onMeasure");
  33.         // 获得它的父容器为它设置的测量模式和大小
  34.         int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
  35.         int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
  36.         int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
  37.         int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
  38.         // 如果是warp_content情况下,记录宽和高
  39.         int width = 0;
  40.         int height = 0;
  41.         //记录每一行的宽度,width不断取最大宽度
  42.         int lineWidth = 0;
  43.         //每一行的高度,累加至height
  44.         int lineHeight = 0;
  45.         int count = getChildCount();
  46.         int left = getPaddingLeft();
  47.         int top = getPaddingTop();
  48.         // 遍历每个子元素
  49.         for (int i = 0; i < count; i++) {
  50.             View child = getChildAt(i);
  51.             if (child.getVisibility() == GONE)
  52.                 continue;
  53.             // 测量每一个child的宽和高
  54.             measureChild(child, widthMeasureSpec, heightMeasureSpec);
  55.             // 得到child的lp
  56.             ViewGroup.LayoutParams lp = child.getLayoutParams();
  57.             // 当前子空间实际占据的宽度
  58.             int childWidth = child.getMeasuredWidth() + childHorizontalSpace;
  59.             // 当前子空间实际占据的高度
  60.             int childHeight = child.getMeasuredHeight() + childVerticalSpace;
  61.             if (lp != null && lp instanceof MarginLayoutParams) {
  62.                 MarginLayoutParams params = (MarginLayoutParams) lp;
  63.                 childWidth += params.leftMargin + params.rightMargin;
  64.                 childHeight += params.topMargin + params.bottomMargin;
  65.             }
  66.             //如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行
  67.             if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) {
  68.                 width = Math.max(lineWidth, childWidth);// 取最大的
  69.                 lineWidth = childWidth; // 重新开启新行,开始记录
  70.                 // 叠加当前高度,
  71.                 height += lineHeight;
  72.                 // 开启记录下一行的高度
  73.                 lineHeight = childHeight;
  74.                 child.setTag(new Location(left, top + height, childWidth + left - childHorizontalSpace, height + child.getMeasuredHeight() + top));
  75.             } else {
  76.                 // 否则累加值lineWidth,lineHeight取最大高度
  77.                 child.setTag(new Location(lineWidth + left, top + height, lineWidth + childWidth - childHorizontalSpace + left, height + child.getMeasuredHeight() + top));
  78.                 lineWidth += childWidth;
  79.                 lineHeight = Math.max(lineHeight, childHeight);
  80.             }
  81.         }
  82.         width = Math.max(width, lineWidth) + getPaddingLeft() + getPaddingRight();
  83.         height += lineHeight;
  84.         sizeHeight += getPaddingTop() + getPaddingBottom();
  85.         height += getPaddingTop() + getPaddingBottom();
  86.         setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height);
  87.     }
  88.     /**
  89.      * 记录子控件的坐标
  90.      */
  91.     public class Location {
  92.         public Location(int left, int top, int right, int bottom) {
  93.             this.left = left;
  94.             this.top = top;
  95.             this.right = right;
  96.             this.bottom = bottom;
  97.         }
  98.         public int left;
  99.         public int top;
  100.         public int right;
  101.         public int bottom;
  102.     }
  103.     //计算当前View以及子View的位置
  104.     @Override
  105.     protected void onLayout(boolean changed, int l, int t, int r, int b) {
  106.         MLog.e(getClass().getName(),"onLayout");
  107.         //获取子View个数
  108.         int count = getChildCount();
  109.         for (int i = 0; i < count; i++) {
  110.             //获取子View
  111.             View child = getChildAt(i);
  112.             //判断是否显示
  113.             if (child.getVisibility() == GONE)
  114.                 continue;
  115.             //获取子View的坐标
  116.             Location location = (Location) child.getTag();
  117.             //设置子View位置
  118.             child.layout(location.left, location.top, location.right, location.bottom);
  119.         }
  120.     }
  121.     @Override
  122.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  123.         super.onSizeChanged(w, h, oldw, oldh);
  124.         MLog.e(getClass().getName(),"onSizeChanged");
  125.     }
  126.     @Override
  127.     protected void onDraw(Canvas canvas) {
  128.         super.onDraw(canvas);
  129.         MLog.e(getClass().getName(),"onDraw");
  130.     }
  131. }

2.使用自定义CustomLayout


  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <com.scc.demo.view.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     xmlns:custom="http://schemas.android.com/apk/res-auto"
  4.     android:layout_width="match_parent"
  5.     android:layout_height="match_parent"
  6.     android:layout_margin="@dimen/dimen_20"
  7.     custom:horizontalSpace="10dp"
  8.     custom:verticalSpace="20dp">
  9.     <!--一定记得添加前缀-->
  10.     <TextView
  11.         style="@style/TvStyle"
  12.         android:text="破阵子·为陈同甫赋壮词以寄" />
  13.     <TextView
  14.         style="@style/TvStyle"
  15.         android:text="宋·辛弃疾" />
  16.     <TextView
  17.         style="@style/TvStyle"
  18.         android:text="醉里挑灯看剑" />
  19.     <TextView
  20.         style="@style/TvStyle"
  21.         android:text="梦回吹角连营" />
  22.     <TextView
  23.         style="@style/TvStyle"
  24.         android:text="八百里分麾下炙" />
  25.     <TextView
  26.         style="@style/TvStyle"
  27.         android:text="五十弦翻塞外声" />
  28.     <TextView
  29.         style="@style/TvStyle"
  30.         android:text="沙场秋点兵" />
  31.     <TextView
  32.         style="@style/TvStyle"
  33.         android:text="马作的卢飞快" />
  34.     <TextView
  35.         style="@style/TvStyle"
  36.         android:text="弓如霹雳弦惊(增加点长度)" />
  37.     <TextView
  38.         style="@style/TvStyle"
  39.         android:text="了却君王天下事" />
  40.     <TextView
  41.         style="@style/TvStyle"
  42.         android:text="赢得生前身后名" />
  43.     <TextView
  44.         style="@style/TvStyle"
  45.         android:text="可怜白发生!" />
  46. </com.scc.demo.view.CustomLayout>

3.自定义属性

在app/src/main/res/values/attrs.xml中添加属性


  
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.     <declare-styleable name="CustomLayout">
  4.         <attr name="verticalSpace" format="dimension" />
  5.         <attr name="horizontalSpace" format="dimension" />
  6.     </declare-styleable>
  7. </resources>

4.使用自定义属性

  • 在xml中使用

一定要添加:xmlns:test=”http://schemas.android.com/apk/res-auto”添加之后才能在xml中自定义属性,如下代码:


  
  1. <com.scc.demo.view.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:custom="http://schemas.android.com/apk/res-auto"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="match_parent"
  5.     android:layout_margin="@dimen/dimen_20"
  6.     custom:horizontalSpace="10dp"
  7.     custom:verticalSpace="20dp">
  8. </com.scc.demo.view.CustomLayout>
  • 在代码中使用


  
  1.  TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.CustomLayout);
  2.         if (attrArray != null) {
  3.             //参数1:获取xml中设置的参数;参数2:获取失败2使用参数作为默认值
  4.             childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_horizontalSpace, 12);
  5.             childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_verticalSpace, 12);
  6.             MLog.e(getClass().getName(),"HorizontalSpace:"+childHorizontalSpace+"|VerticalSpace:"+childVerticalSpace);
  7.             //TypedArray对象池的大小默认为5,使用时记得调用recyle()方法将不用的对象返回至对象池来达到重用的目的。
  8.             attrArray.recycle();
  9.         }

        写到这里自定义ViewGroup基本完成。

ViewGroup属性

ViewGroup的XML属性以及相关方法

XML属性

相关方法

说明

android:addStatesFromChildren

设置此 ViewGroup 的可绘制状态是否还包括其子级的可绘制状态。

android:alwaysDrawnWithCache

定义 ViewGroup 是否应始终使用其绘图缓存绘制其子项。

android:animateLayoutChanges

setLayoutTransition
(LayoutTransition)

定义布局更改(由添加和删除项目引起)是否应导致 LayoutTransition 运行。

android:animationCache

定义布局动画是否应为其子项创建绘图缓存。

android:clipChildren

setClipChildren(boolean)

定义孩子是否被限制在其边界内绘制。

android:clipToPadding

setClipToPadding(boolean)

如果填充不为零,则定义 ViewGroup 是否将裁剪其子项并将任何 EdgeEffect 调整大小(但不裁剪)到其填充。

android:descendantFocusability

定义 ViewGroup 和它的后代在寻找一个 View 来获得焦点时的关系。

android:layoutAnimation

定义第一次布局 ViewGroup 时使用的布局动画。

android:layoutMode

setLayoutMode(int)

定义此 ViewGroup 的布局模式。

android:persistentDrawingCache

定义绘图缓存的持久性。

android:splitMotionEvents

setMotionEventSplittingEnabled
(boolean)

设置此 ViewGroup 是否应在触摸事件调度期间拆分 MotionEvents 以分隔子视图。

 ViewGroup.LayoutParams  

        LayoutParams 被视图用来告诉他们的父母他们想要如何布局。

        基本的 LayoutParams 类只是描述了视图的宽度和高度的大小。对于每个维度,它可以指定以下之一:

        1.MATCH_PARENT,这意味着视图希望与其父视图一样大(减去填充)

        2.WRAP_CONTENT,这意味着视图希望足够大以包含其内容(加上填充)

        3.确切的数字

        ViewGroup的不同子类都有LayoutParams的子类。例如,LinearLayout有自己的 LayoutParams子类.

        ViewGroup.LayoutParams(子组件)的XML属性

XML属性

说明

android:layout_height

指定视图的基本高度。

android:layout_width

指定视图的基本宽度。

 ViewGroup.MarginLayoutParams 

        支持边距的布局的每个子布局信息。

        ViewGroup.MarginLayoutParams(子组件)的XML属性及相关方法

XML属性

相关方法

说明

android:layout_margin

指定此视图左侧、顶部、右侧和底部的额外空间。

android:layout_marginBottom

setMargins(int,int,int,int)

指定此视图底部的额外空间。  

android:layout_marginEnd

setMarginEnd(int)

指定此视图末端的额外空间。

android:layout_marginHorizontal

指定此视图左侧和右侧的额外空间。

android:layout_marginLeft

setMargins(int,int,int,int)

指定此视图左侧的额外空间。

android:layout_marginRight

setMargins(int,int,int,int)

指定此视图右侧的额外空间。

android:layout_marginStart

setMarginStart(int)

指定此视图开始侧的额外空间。

android:layout_marginTop

setMargins(int,int,int,int)

指定此视图顶部的额外空间。

android:layout_marginVertical

指定此视图顶部和底部的额外空间。

文章来源: shuaici.blog.csdn.net,作者:帅次,版权归原作者所有,如需转载,请联系作者。

原文链接:shuaici.blog.csdn.net/article/details/117562492

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。