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
/** * 编写自定义ViewGroup的示例。 */ public class CustomLayout extends ViewGroup { // private int childHorizontalSpace = 20; // private int childVerticalSpace = 20; private int childHorizontalSpace; private int childVerticalSpace; //从代码创建视图时使用的简单构造函数。 public CustomLayout(Context context) { super(context); } //从XML使用视图时调用的构造函数。 public CustomLayout(Context context, AttributeSet attrs) { super(context, attrs); TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.CustomLayout); if (attrArray != null) { childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_horizontalSpace, 12); childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_verticalSpace, 12); MLog.e(getClass().getName(),"HorizontalSpace:"+childHorizontalSpace+"|VerticalSpace:"+childVerticalSpace); attrArray.recycle(); } //此视图是否自行绘制 setWillNotDraw(false); } /** * 负责设置子控件的测量模式和大小 根据所有子控件设置自己的宽和高 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); MLog.e(getClass().getName(),"onMeasure"); // 获得它的父容器为它设置的测量模式和大小 int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); int modeWidth = MeasureSpec.getMode(widthMeasureSpec); int modeHeight = MeasureSpec.getMode(heightMeasureSpec); // 如果是warp_content情况下,记录宽和高 int width = 0; int height = 0; //记录每一行的宽度,width不断取最大宽度 int lineWidth = 0; //每一行的高度,累加至height int lineHeight = 0; int count = getChildCount(); int left = getPaddingLeft(); int top = getPaddingTop(); // 遍历每个子元素 for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() == GONE) continue; // 测量每一个child的宽和高 measureChild(child, widthMeasureSpec, heightMeasureSpec); // 得到child的lp ViewGroup.LayoutParams lp = child.getLayoutParams(); // 当前子空间实际占据的宽度 int childWidth = child.getMeasuredWidth() + childHorizontalSpace; // 当前子空间实际占据的高度 int childHeight = child.getMeasuredHeight() + childVerticalSpace; if (lp != null && lp instanceof MarginLayoutParams) { MarginLayoutParams params = (MarginLayoutParams) lp; childWidth += params.leftMargin + params.rightMargin; childHeight += params.topMargin + params.bottomMargin; } //如果加入当前child,则超出最大宽度,则的到目前最大宽度给width,类加height 然后开启新行 if (lineWidth + childWidth > sizeWidth - getPaddingLeft() - getPaddingRight()) { width = Math.max(lineWidth, childWidth);// 取最大的 lineWidth = childWidth; // 重新开启新行,开始记录 // 叠加当前高度, height += lineHeight; // 开启记录下一行的高度 lineHeight = childHeight; child.setTag(new Location(left, top + height, childWidth + left - childHorizontalSpace, height + child.getMeasuredHeight() + top)); } else { // 否则累加值lineWidth,lineHeight取最大高度 child.setTag(new Location(lineWidth + left, top + height, lineWidth + childWidth - childHorizontalSpace + left, height + child.getMeasuredHeight() + top)); lineWidth += childWidth; lineHeight = Math.max(lineHeight, childHeight); } } width = Math.max(width, lineWidth) + getPaddingLeft() + getPaddingRight(); height += lineHeight; sizeHeight += getPaddingTop() + getPaddingBottom(); height += getPaddingTop() + getPaddingBottom(); setMeasuredDimension((modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height); } /** * 记录子控件的坐标 */ public class Location { public Location(int left, int top, int right, int bottom) { this.left = left; this.top = top; this.right = right; this.bottom = bottom; } public int left; public int top; public int right; public int bottom; } //计算当前View以及子View的位置 @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { MLog.e(getClass().getName(),"onLayout"); //获取子View个数 int count = getChildCount(); for (int i = 0; i < count; i++) { //获取子View View child = getChildAt(i); //判断是否显示 if (child.getVisibility() == GONE) continue; //获取子View的坐标 Location location = (Location) child.getTag(); //设置子View位置 child.layout(location.left, location.top, location.right, location.bottom); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); MLog.e(getClass().getName(),"onSizeChanged"); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); MLog.e(getClass().getName(),"onDraw"); } }
2.使用自定义CustomLayout
<?xml version="1.0" encoding="utf-8"?> <com.scc.demo.view.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/dimen_20" custom:horizontalSpace="10dp" custom:verticalSpace="20dp"> <!--一定记得添加前缀--> <TextView style="@style/TvStyle" android:text="破阵子·为陈同甫赋壮词以寄" /> <TextView style="@style/TvStyle" android:text="宋·辛弃疾" /> <TextView style="@style/TvStyle" android:text="醉里挑灯看剑" /> <TextView style="@style/TvStyle" android:text="梦回吹角连营" /> <TextView style="@style/TvStyle" android:text="八百里分麾下炙" /> <TextView style="@style/TvStyle" android:text="五十弦翻塞外声" /> <TextView style="@style/TvStyle" android:text="沙场秋点兵" /> <TextView style="@style/TvStyle" android:text="马作的卢飞快" /> <TextView style="@style/TvStyle" android:text="弓如霹雳弦惊(增加点长度)" /> <TextView style="@style/TvStyle" android:text="了却君王天下事" /> <TextView style="@style/TvStyle" android:text="赢得生前身后名" /> <TextView style="@style/TvStyle" android:text="可怜白发生!" /> </com.scc.demo.view.CustomLayout>
3.自定义属性
在app/src/main/res/values/attrs.xml中添加属性
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CustomLayout"> <attr name="verticalSpace" format="dimension" /> <attr name="horizontalSpace" format="dimension" /> </declare-styleable> </resources>
4.使用自定义属性
- 在xml中使用
一定要添加:xmlns:test=”http://schemas.android.com/apk/res-auto”添加之后才能在xml中自定义属性,如下代码:
<com.scc.demo.view.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="@dimen/dimen_20" custom:horizontalSpace="10dp" custom:verticalSpace="20dp"> </com.scc.demo.view.CustomLayout>
- 在代码中使用
TypedArray attrArray = context.obtainStyledAttributes(attrs, R.styleable.CustomLayout); if (attrArray != null) { //参数1:获取xml中设置的参数;参数2:获取失败2使用参数作为默认值 childHorizontalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_horizontalSpace, 12); childVerticalSpace = attrArray.getDimensionPixelSize(R.styleable.CustomLayout_verticalSpace, 12); MLog.e(getClass().getName(),"HorizontalSpace:"+childHorizontalSpace+"|VerticalSpace:"+childVerticalSpace); //TypedArray对象池的大小默认为5,使用时记得调用recyle()方法将不用的对象返回至对象池来达到重用的目的。 attrArray.recycle(); }
写到这里自定义ViewGroup基本完成。
ViewGroup属性
ViewGroup的XML属性以及相关方法
XML属性 |
相关方法 |
说明 |
android:addStatesFromChildren |
设置此 ViewGroup 的可绘制状态是否还包括其子级的可绘制状态。 |
|
android:alwaysDrawnWithCache |
定义 ViewGroup 是否应始终使用其绘图缓存绘制其子项。 |
|
android:animateLayoutChanges |
setLayoutTransition |
定义布局更改(由添加和删除项目引起)是否应导致 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 |
设置此 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 |
指定此视图顶部和底部的额外空间。 |