自定义ViewGroup首先需要做什么
1.要继承ViewGroup
2.重写四个构造方法
3.重写 onMeasure(),onLayout()两个方法
4.在onMeasure()中递归计算子View以及本身的大小
5.在onLayout()中绘制布局
下面详细讲解每一步骤
继承ViewGroup以及重写构造方法和onMeasure(),onLayout()方法
public class flowlayout extends ViewGroup
{
//一个参数的构造方法:在new一个实例的时候会调用
public flowlayout(Context context)
{
super(context);
}
//两个参数的构造方法:用来解析XML,在运行过程中运用发射进行解析
public flowlayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
//三个参数的构造方法:style主题相关,用来决定你的style主题样式
public flowlayout(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
//四个参数的构造方法:解析XML中的自定义属性
public flowlayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
//这个方法是用来递归测量子View 以及自身的大小的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
}
//这个方法是用来绘制子View的位置的
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
}
}
怎么递归测量子View以及自己的大小
一个ViewGroup下面可以有很多View以及ViwGroup,当顶层ViewGroup开始确定自己的大小的时候他会先测量自己的孩子的大小,最终来确定自己的大小(红色表示去测量子View的大小,蓝色表示子View返回给父View自己的结果)
MeasureSpec的组成模式
MeasureSpece是一个整形int值,一共32位,高2位表示测量模式,低30位表示View的大小
1.高两位表示模式
UNSPECIFIED:不对View大小做限制,系统使用
EXACTLY:View的大小是精准大小
AT_MOST:大小不可超过某个值,入match_Parent,最大不能超过父View
2.低30位
表示大小(Size)
进行测量
public class flowlayout extends ViewGroup
{
private List<List<View>> allLines = new ArrayList<>();//记录所有行的View
private List<Integer> lineHeightMax = new ArrayList<>();//记录每行的最大高度
int horizontalSpacing = 10;//两个View之间的空格
int verticalHeight = 10;//两行之间的空格
//..............
//..............
//省略不重要代码
//..............
private void cleaar()
{
allLines.clear();
lineHeightMax.clear();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
//清理,为什么进行清理呢,因为父View在调用子View的onMeasure()方法的时候可能不止调用一次,所以每次都要清理,记录下来的数据
cleaar();
// TODO 整体框架
// TODO 先测量子View
// TODO 在测量父View
List<View> lineViews = new ArrayList<>();//记录当前行所有View
int lineWidthUsed = 0;//记录当前行使用了多少宽度
int lineHeight = 0;//记录当前行的高度,当前行所有View中最高的高度,为当前行的高度
int parentNeedWidth = 0;//父View需要的宽度
int parentNeedHeight = 0;//父View需要的高度
//获取父View的高/宽度,以及父View的测量模式
int widthModel = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightModel = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//获取父View的内边距
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//先测量孩子
int size = getChildCount();
for (int i = 0; i < size; i++)
{
View childView = getChildAt(i);
final LayoutParams lp = childView.getLayoutParams();
//如果子View隐藏且不占用距离,则不计算他的高度和宽度
if (childView.getVisibility() == View.GONE)
{
continue;
}
/**
* 这个方法是获得子View的的大小
* 第一个参数是父View的宽,在父View的基础上我能给子View分配多少宽度
* 第二个参数是父View的左右内边距,再给子View分配宽度的时候需要去掉父View的内边距
* 第三个参数是子View的宽度
*/
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height);
//对孩子进行测量
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//获得子View的高度和宽度
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
/**
* 如果子View的宽度+已使用过得宽度+空格>=父View的宽度----->换行
*
*/
if (childWidth + lineWidthUsed + horizontalSpacing >= widthSize)
{
//记录父View需要的宽度和高度
parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
parentNeedHeight += lineHeight + verticalHeight;
//记录每一行的View和每一行的最大高度
allLines.add(lineViews);
lineHeightMax.add(lineHeight);
//对数据进行清零
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
//不换行
lineViews.add(childView);//记住当前行所有的View
lineWidthUsed += childWidth + horizontalSpacing;//已经使用的宽度,
lineHeight = Math.max(lineHeight, childHeight); //获取当前行View最大的高度
}
//把最后一行加入记录中
parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
parentNeedHeight += lineHeight + verticalHeight;
allLines.add(lineViews);
lineHeightMax.add(lineHeight);
//TODO 测量父View自己
//根据父View自己的模式来判断父View的宽和高
//如果父View的宽和高都是精准的值,则是他们本身的大小
//否则就是根据子View的大小进行确定
int realWidth = widthModel == MeasureSpec.EXACTLY ? widthSize : parentNeedWidth;
int realHeight = heightModel == MeasureSpec.EXACTLY ? heightSize : parentNeedHeight;
setMeasuredDimension(realWidth, realHeight);
}
//..............
//..............
//省略不重要代码
//..............
}
onLayout() 绘制View
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int left = getPaddingLeft();
int top = getPaddingTop();
int right = 0;
int bottom = 0;
int lineCount = allLines.size();
for (int i = 0; i < lineCount; i++)
{
//如果父View有gravity属性,则left,top,right,bottom还需要考虑gravity的属性
int currentHeight = lineHeightMax.get(i);
List<View> lineView = allLines.get(i);
int childCount = lineView.size();
for (int j = 0; j < childCount; j++)
{
View view = lineView.get(j);
right = left + view.getMeasuredWidth();
bottom = top + currentHeight;
view.layout(left, top, right, bottom);
left = right + horizontalSpacing;
}
top += currentHeight + verticalHeight;
left = getPaddingLeft();
}
}
请大家多多指教
完整代码
package com.json.viewgroup;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
public class flowlayout extends ViewGroup
{
private List<List<View>> allLines = new ArrayList<>();
private List<Integer> lineHeightMax = new ArrayList<>();
int horizontalSpacing = 10;//两个View之间的空格
int verticalHeight = 10;//两行之间的空格
public flowlayout(Context context)
{
super(context);
}
public flowlayout(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public flowlayout(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}
public flowlayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
{
super(context, attrs, defStyleAttr, defStyleRes);
}
private void cleaar()
{
allLines.clear();
lineHeightMax.clear();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
//清理,为什么进行清理呢,因为父View在调用子View的onMeasure()方法的时候可能不止调用一次,所以每次都要清理,记录下来的数据
cleaar();
// TODO 整体框架
// TODO 先测量子View
// TODO 在测量父View
List<View> lineViews = new ArrayList<>();//记录当前行所有View
int lineWidthUsed = 0;//记录当前行使用了多少宽度
int lineHeight = 0;//记录当前行的高度
int parentNeedWidth = 0;//父View需要的宽度
int parentNeedHeight = 0;//父View需要的高度
//获取父View的高/宽度,以及父View的测量模式
int widthModel = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightModel = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//获取父View的内边距
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
//先度量孩子
int size = getChildCount();
for (int i = 0; i < size; i++)
{
View childView = getChildAt(i);
final LayoutParams lp = childView.getLayoutParams();
//如果子View隐藏且不占用距离,则不计算他的高度和宽度
if (childView.getVisibility() == View.GONE)
{
continue;
}
/**
* 这个方法是获得子View的的大小
* 第一个参数是父View的宽,在父View的基础上我能给子View分配多少宽度
* 第二个参数是父View的左右内边距,再给子View分配宽度的时候需要去掉父View的内边距
* 第三个参数是子View的宽度
*/
int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, paddingLeft + paddingRight, lp.width);
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, paddingTop + paddingBottom, lp.height);
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//获得子View的高度和宽度
int childWidth = childView.getMeasuredWidth();
int childHeight = childView.getMeasuredHeight();
/**
* 如果子View的宽度+已使用过得宽度+空格>=父View的宽度----->换行
*
*/
if (childWidth + lineWidthUsed + horizontalSpacing >= widthSize)
{
parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
parentNeedHeight += lineHeight + verticalHeight;
allLines.add(lineViews);
lineHeightMax.add(lineHeight);
lineViews = new ArrayList<>();
lineWidthUsed = 0;
lineHeight = 0;
}
lineViews.add(childView);
lineWidthUsed += childWidth + horizontalSpacing;
lineHeight = Math.max(lineHeight, childHeight); //获取当前行最大的高度
}
parentNeedWidth = Math.max(parentNeedWidth, lineWidthUsed + horizontalSpacing);
parentNeedHeight += lineHeight + verticalHeight;
allLines.add(lineViews);
lineHeightMax.add(lineHeight);
//TODO 测量父View自己
//根据父View自己的模式来判断父View的宽和高
//如果父View的宽和高都是精准的值,则是他们本身的大小
//否则就是根据子View的大小进行确定
int realWidth = widthModel == MeasureSpec.EXACTLY ? widthSize : parentNeedWidth;
int realHeight = heightModel == MeasureSpec.EXACTLY ? heightSize : parentNeedHeight;
setMeasuredDimension(realWidth, realHeight);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b)
{
int left = getPaddingLeft();
int top = getPaddingTop();
int right = 0;
int bottom = 0;
int lineCount = allLines.size();
for (int i = 0; i < lineCount; i++)
{
//如果父View有gravity属性,则left,top,right,bottom还需要考虑gravity的属性
int currentHeight = lineHeightMax.get(i);
List<View> lineView = allLines.get(i);
int childCount = lineView.size();
for (int j = 0; j < childCount; j++)
{
View view = lineView.get(j);
right = left + view.getMeasuredWidth();
bottom = top + currentHeight;
view.layout(left, top, right, bottom);
left = right + horizontalSpacing;
}
top += currentHeight + verticalHeight;
left = getPaddingLeft();
}
}
}