支持wrap_content
即当ViewGroup的宽、高使用wrap-content时,ViewGroup的高宽根据子View的实际大小来肯定
如果你不处理的话,“wrap-content”的和 “match-parent”是1样的
ViewGroup支持Padding
其子View支持margin
支持自定义属性
例如:gravity
滑动事件冲突处理
touch事件分发
接下来,我们1步1步实现以上内容
onMeasure的写法
onLayout的写法
onMeasure中widthMeasureSpec、heightMeasureSpec的理解
这里不具体解释这个问题,只需知道,就是由于MeasureSpec的规则致使了,如果你不进行特殊处理,导致wrap-content的效果为match-parent
展开说1句:儿子的尺寸,不完全由儿子自己决定,与父亲传递过来的丈量规格是有关系的
onLayout中l,t,r,b的含义
解释1下:int l, int t, int r, int b,这4个值
这4个值,就是通过onMeasure方法丈量后,得到的这个MyViewGroup的左上右下值
注意:l,t,r,b是斟酌了其所在的父ViewGroup的padding值的(MyviewGroup本身的padding值,固然也斟酌进去了)
这4个值,大家可以打印出来看看就明白了,这个不清楚的话,很难写好onLayout方法
l=自定义ViewGroup的父ViewGroup的leftPadding
t=自定义ViewGroup的父ViewGroup的topPadding
r=自定义ViewGroup的父ViewGroup的leftPadding+这个MyViewGroup的宽度(即上文的desireWidth)
t=自定义ViewGroup的父ViewGroup的topPadding+这个MyViewGroup的高度(即上文的desireHeight)
大家都知道,自定义View要经过onMeasure、onLayout、onDraw3个流程
要丈量其内部所有的子View的宽高
measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec)
measure(int widthMeasureSpec, int heightMeasureSpec)
上面两个方法都可使用,意思是1样的
丈量ViewGroup自己的宽高
你要是懒得支持wrap_content,只需要写
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
便可,
会调用父类的流程去丈量你自定义的ViewGroup的宽高(不具体展开源码,源码其实调用的是setMeasuredDimension(int measuredWidth, int measuredHeight)
)
不支持wrap-content的意思就说:当你设置宽高为wrap-content时候,实际的效果却是match_content
你要是支持wrap_content的话,
使用下面的方法来丈量ViewGroup本身的宽高
setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),
resolveSize(desireHeight, heightMeasureSpec));
你肯定想当你的ViewGroup宽度为wrap-content时,这个ViewGroup的宽度值是所有子View的宽度之和再加上左右padding(先不斟酌子View的margin),
这就需要你在丈量每个子View的宽度时,累加所有的子View宽度再加上左右padding值作为ViewGroup的宽度(当ViewGroup的宽度值你设置的是wrap_content时,你要是设100dp,那就直接是100dp了),
由于本例是横向排列的ViewGroup,所以,ViewGroup的高度就是子View里最高的那个子View的高度再加上上下padding了
代码里的:
desireWidth=所有子View的宽度累加上和+左右padding
desireHeight=最高的那个子View的高度+上下padding
onMeasure模板代码:(只是支持wrap_content和padding,还不支持margin)
` @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ★1. 计算所有child view 要占用的空间
desireWidth = 0;//累加所有子View的宽度,作为MyViewGroup宽度设为wrap_content时的宽度
desireHeight = 0;//本例子是横向排列的ViewGroup,所以,MyViewGroup高度设为wrap_content时,其高度是其所有子View中最高子View的高度
int count = getChildCount();
for (int i = 0; i < count; ++i) {
View v = getChildAt(i);
//1.1 开始遍历丈量所有的子View,并且根据实际情况累加子View的宽(或高),为了计算全部viewgroup的宽度(高度)
if (v.getVisibility() != View.GONE) {//不去丈量Gone的子View
measureChild(v, widthMeasureSpec,
heightMeasureSpec);
--------------只需要根据你的需求,更改虚线内部的代码便可,其余的不用改--------------
//由于横向排列,累加所有的子View的宽度
desireWidth += v.getMeasuredWidth();
//高度是子View中最高的高度
desireHeight = Math
.max(desireHeight, v.getMeasuredHeight());
--------------只需要根据你的需求,更改虚线内部的代码便可,其余的不用改--------------
}
}
// 1.2 斟酌padding值
//到目前为止desireWidth为所有子View的宽度的累加,作为MyViewGroup的总宽度,要加上左右padding值
desireWidth += getPaddingLeft() + getPaddingRight();
//高度同理略
desireHeight += getPaddingTop() + getPaddingBottom();
//★2.丈量ViewGroup的宽高,如果不写这1步,使用wrap_content时效果为match_parent的效果
// (下面的写法比较简洁,《Android群英传》介绍了另外1种写法,比这个略微麻烦1点)
// see if the size is big enough
desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
// super.onMeasure(widthMeasureSpec,heightMeasureSpec);
setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),
resolveSize(desireHeight, heightMeasureSpec));
}`
` @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//这个自定义的ViewGroup的有效内容的4个边界
final int parentLeft = getPaddingLeft();//这个MyViewGroup的最左侧的距离(纯洁的内容的左侧界,不含padding)
final int parentTop = getPaddingTop();//这个MyViewGroup的最上边的距离(纯洁的内容的上边界,不含padding)
final int parentRight = r - l - getPaddingRight();//这个MyViewGroup的最右侧的距离(纯洁的内容的右侧界,不含padding)
final int parentBottom = b - t - getPaddingBottom();//这个MyViewGroup的最下边的距离(纯洁的内容的下边界,不含padding)
if (BuildConfig.DEBUG)
Log.d("onlayout", "parentleft: " + parentLeft + " parenttop: "
+ parentTop + " parentright: " + parentRight
+ " parentbottom: " + parentBottom+"\n"+ " l: " + l+ " t: " + t+ " r: " + r+ " b: " + b);
int left = parentLeft;
int top = parentTop;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
//得到每个子View的丈量后的宽高
final int childWidth = v.getMeasuredWidth();
final int childHeight = v.getMeasuredHeight();
//开始布局每个子View(左、上、右=左+子View宽、下=上+子View高)
v.layout(left, top, left + childWidth, top + childHeight);
//由于本例是横向排列的,所以每个子View的left值要递增
left += childWidth;
}
}
}`
完全代码:
`
public class MyViewGroup extends ViewGroup {
private int desireWidth;
private int desireHeight;
public MyViewGroup(Context context) {
this(context, null);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ★1. 计算所有child view 要占用的空间
desireWidth = 0;//累加所有子View的宽度,作为MyViewGroup宽度设为wrap_content时的宽度
desireHeight = 0;//本例子是横向排列的ViewGroup,所以,MyViewGroup高度设为wrap_content时,其高度是其所有子View中最高子View的高度
int count = getChildCount();
for (int i = 0; i < count; ++i) {
View v = getChildAt(i);
//1.1 开始遍历丈量所有的子View,并且根据实际情况累加子View的宽(或高),为了计算全部viewgroup的宽度(高度)
if (v.getVisibility() != View.GONE) {//不去丈量Gone的子View
measureChild(v, widthMeasureSpec,
heightMeasureSpec);
//由于横向排列,累加所有的子View的宽度
desireWidth += v.getMeasuredWidth();
//高度是子View中最高的高度
desireHeight = Math
.max(desireHeight, v.getMeasuredHeight());
}
}
// 1.2 斟酌padding值
//到目前为止desireWidth为所有子View的宽度的累加,作为MyViewGroup的总宽度,要加上左右padding值
desireWidth += getPaddingLeft() + getPaddingRight();
//高度同理略
desireHeight += getPaddingTop() + getPaddingBottom();
//★2.丈量ViewGroup的宽高,如果不写这1步,使用wrap_content时效果为match_parent的效果
// (下面的写法比较简洁,《Android群英传》介绍了另外1种写法,比这个略微麻烦1点)
// see if the size is big enough
desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
//super.onMeasure(widthMeasureSpec,heightMeasureSpec);
setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),
resolveSize(desireHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//这个自定义的ViewGroup的有效内容的4个边界
final int parentLeft = getPaddingLeft();//这个MyViewGroup的最左侧的距离(纯洁的内容的左侧界,不含padding)
final int parentTop = getPaddingTop();//这个MyViewGroup的最上边的距离(纯洁的内容的上边界,不含padding)
final int parentRight = r - l - getPaddingRight();//这个MyViewGroup的最右侧的距离(纯洁的内容的右侧界,不含padding)
final int parentBottom = b - t - getPaddingBottom();//这个MyViewGroup的最下边的距离(纯洁的内容的下边界,不含padding)
if (BuildConfig.DEBUG)
Log.d("onlayout", "parentleft: " + parentLeft + " parenttop: "
+ parentTop + " parentright: " + parentRight
+ " parentbottom: " + parentBottom + "\n" + " l: " + l + " t: " + t + " r: " + r + " b: " + b);
int left = parentLeft;
int top = parentTop;
int count = getChildCount();
for (int i = 0; i < count; i++) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
//得到每个子View的丈量后的宽高
final int childWidth = v.getMeasuredWidth();
final int childHeight = v.getMeasuredHeight();
//开始布局每个子View(左、上、右=左+子View宽、下=上+子View高)
v.layout(left, top, left + childWidth, top + childHeight);
//由于本例是横向排列的,所以每个子View的left值要递增
left += childWidth;
}
}
}
}
`
1般情况,自定义ViewGroup支持wrap-content和padding就够用了
每个自定义ViewGroup都必须,自定义这个类LayoutParams,和后面的3个方法,否则强转报异常,
其余没甚么好说的,计算距离,布局的时候把margin斟酌进去便可
`public class MyViewGroupMargin extends ViewGroup {
private int desireWidth;
private int desireHeight;
public MyViewGroupMargin(Context context) {
this(context, null);
}
public MyViewGroupMargin(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// ★1. 计算所有child view 要占用的空间
desireWidth = 0;//累加所有子View的宽度,作为MyViewGroup宽度设为wrap_content时的宽度
desireHeight = 0;//本例子是横向排列的ViewGroup,所以,MyViewGroup高度设为wrap_content时,其高度是其所有子View中最高子View的高度
int count = getChildCount();
for (int i = 0; i < count; ++i) {
View v = getChildAt(i);
//1.1 开始遍历丈量所有的子View,并且根据实际情况累加子View的宽(或高),为了计算全部viewgroup的宽度(高度)
if (v.getVisibility() != View.GONE) {//不去丈量Gone的子View
LayoutParams lp = (LayoutParams) v.getLayoutParams();
//▲变化1:将measureChild改成measureChildWithMargin
measureChildWithMargins(v, widthMeasureSpec, 0,
heightMeasureSpec, 0);
/*原来: measureChild(v, widthMeasureSpec,
heightMeasureSpec);*/
//▲变化2:这里在累加所有的子View的宽度时加上他自己的margin
desireWidth += v.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
desireHeight = Math
.max(desireHeight, v.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
/*原来://由于横向排列,累加所有的子View的宽度
desireWidth += v.getMeasuredWidth();
//高度是子View中最高的高度
desireHeight = Math
.max(desireHeight, v.getMeasuredHeight());*/
}
}
// 1.2 斟酌padding值
//到目前为止desireWidth为所有子View的宽度的累加,作为MyViewGroup的总宽度,要加上左右padding值
desireWidth += getPaddingLeft() + getPaddingRight();
//高度同理略
desireHeight += getPaddingTop() + getPaddingBottom();
//★2.丈量ViewGroup的宽高,如果不写这1步,使用wrap_content时效果为match_parent的效果
// (下面的写法比较简洁,《Android群英传》介绍了另外1种写法,比这个略微麻烦1点)
// see if the size is big enough
desireWidth = Math.max(desireWidth, getSuggestedMinimumWidth());
desireHeight = Math.max(desireHeight, getSuggestedMinimumHeight());
setMeasuredDimension(resolveSize(desireWidth, widthMeasureSpec),
resolveSize(desireHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final int parentLeft = getPaddingLeft();
final int parentRight = r - l - getPaddingRight();
final int parentTop = getPaddingTop();
final int parentBottom = b - t - getPaddingBottom();
if (BuildConfig.DEBUG)
Log.d("onlayout", "parentleft: " + parentLeft + " parenttop: "
+ parentTop + " parentright: " + parentRight
+ " parentbottom: " + parentBottom);
int left = parentLeft;
int top = parentTop;
int count = getChildCount();
for (int i = 0; i < count; ++i) {
View v = getChildAt(i);
if (v.getVisibility() != View.GONE) {
LayoutParams lp = (LayoutParams) v.getLayoutParams();
final int childWidth = v.getMeasuredWidth();
final int childHeight = v.getMeasuredHeight();
//▲变化1:左边要加上这个子View的左边margin
left += lp.leftMargin;
//▲变化2:上侧要加上子View的margin
top = parentTop + lp.topMargin;
if (BuildConfig.DEBUG) {
Log.d("onlayout", "child[width: " + childWidth
+ ", height: " + childHeight + "]");
Log.d("onlayout", "child[left: " + left + ", top: "
+ top + ", right: " + (left + childWidth)
+ ", bottom: " + (top + childHeight));
}
v.layout(left, top, left + childWidth, top + childHeight);
//▲变化3:由于是横向排列的,所以下1个View的左边加上这个view的右边的margin(如果是纵向排列的则对应改变top)
left += childWidth + lp.rightMargin;
}
}
}
//★★★★★★★★★★★★★★★★★★★★★★★要使用margin必须写下面的方法★★★★★★★★★★★★★★★★★★★★★
//***开始***每个自定义ViewGroup都必须,自定义这个类LayoutParams,和后面的3个方法,否则强转报异常,模板代码照抄便可**************
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
}
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT);
}
@Override
public ViewGroup.LayoutParams generateLayoutParams(
AttributeSet attrs) {
return new LayoutParams(getContext(), attrs);
}
@Override
protected ViewGroup.LayoutParams generateLayoutParams(
ViewGroup.LayoutParams p) {
return new LayoutParams(p);
}
//***结束***每个自定义ViewGroup都必须,自定义这个类LayoutParams,和后面的3个方法,否则强转报异常,模板代码照抄便可**************
}
`