小Demo代码-自定义ViewGroup
ViewGroup是个View容器,它装纳child View并且负责把child View放入指定的位置。
设计步骤:
- 首先,我们得知道各个子View的大小吧,只有先知道子View的大小,我们才知道当前的ViewGroup该设置为多大去容纳它们。
- 根据子View的大小,以及我们的ViewGroup要实现的功能,决定出ViewGroup的大小
- ViewGroup和子View的大小算出来了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得根据你定制的需求去摆放了,比如,你想让子View按照垂直顺序一个挨着一个放,或者是按照先后顺序一个叠一个去放,这是你自己决定的。
- 已经知道怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间”分割”成大大小小的空间,每个空间对应一个子View,我们接下来就是把子View对号入座了,把它们放进它们该放的地方去。
具体的案例:将子View按从上到下垂直顺序一个挨着一个摆放,即模仿实现LinearLayout的垂直布局。
首先重写onMeasure,实现测量子View大小以及设定ViewGroup的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//将所有的子View进行测量,这会触发每个子View的onMeasure函数
//注意要与measureChild区分,measureChild是对单个view进行测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
setMeasuredDimension(0, 0);
} else {
//如果宽高都是包裹内容
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容
//宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
setMeasuredDimension(widthSize, getTotleHeight());
} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容
//宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
setMeasuredDimension(getMaxChildWidth(), heightSize);
}
}
}
/***
* 获取子View中宽度最大的值
*/
private int getMaxChildWidth() {
int childCount = getChildCount();
int maxWidth = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > maxWidth)
maxWidth = childView.getMeasuredWidth();
}
return maxWidth;
}
/***
* 将所有子View的高度相加
**/
private int getTotleHeight() {
int childCount = getChildCount();
int height = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
height += childView.getMeasuredHeight();
}
return height;
}
上面的onMeasure将子View测量好了,以及把自己的尺寸也设置好了,接下来摆放子View。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//记录当前的高度位置
int curHeight = t;
//将子View逐个摆放
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
//摆放子View,参数分别是子View矩形区域的左、上、右、下边
child.layout(l, curHeight, l + width, curHeight + height);
curHeight += height;
}
}
将自定义的ViewGroup里面放3个Button ,将这3个Button的宽度设置不一样,把ViewGroup的宽高都设置为包裹内容wrap_content,为了看的效果明显,给ViewGroup加个背景:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hc.studyview.MyViewGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff9900">
<Button
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="btn" />
</com.hc.studyview.MyViewGroup>
</LinearLayout>
MyViewGroup的完整代码:
package com.hc.studyview;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
/**
* Package com.hc.studyview
* Created by HuaChao on 2016/6/3.
*/
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
/***
* 获取子View中宽度最大的值
*/
private int getMaxChildWidth() {
int childCount = getChildCount();
int maxWidth = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > maxWidth)
maxWidth = childView.getMeasuredWidth();
}
return maxWidth;
}
/***
* 将所有子View的高度相加
**/
private int getTotleHeight() {
int childCount = getChildCount();
int height = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
height += childView.getMeasuredHeight();
}
return height;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//将所有的子View进行测量,这会触发每个子View的onMeasure函数
//注意要与measureChild区分,measureChild是对单个view进行测量
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
setMeasuredDimension(0, 0);
} else {
//如果宽高都是包裹内容
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容
//宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
setMeasuredDimension(widthSize, getTotleHeight());
} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容
//宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
setMeasuredDimension(getMaxChildWidth(), heightSize);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//记录当前的高度位置
int curHeight = t;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
child.layout(0, curHeight,width, curHeight + height);
curHeight += height;
}
}
}