如今需求的多种多样,app要求各种绚丽的效果,使得andriod开发的我们无法避免一个问题,最终逼得我们不得不去自定义一些view来实现需求。
阅读了下 Android 我眼中的自定义View(2) 这篇文章,给自己做一个笔记。总结了下:
通常自定义view有一下几种:
1、直接继承View,或者ViewGroup去重写一个自定义view。
2、继承某个已有的控件,比如TextView去重写一个自定义view。
3、继承某个已有的ViewGroup,比如LinearLayout去重写一个自定义view。
对于第一种方式,需要注意的是,在内容区域未超过屏幕尺寸的情况下,我们一般需要在onMeasure()中重新测量ViewGroup尺寸来对wrap_content属性进行支持,如果内容区域的大小超过屏幕尺寸,我们就必须在onMeasure()中重新测量ViewGroup的尺寸,否则ViewGroup的最大尺寸为屏幕尺寸,导致ViewGroup中的内容显示不全。因为,此时wrap_content和match_parent的效果是一样的,同时各种padding也是无效的,因此是需要你自己onMeasure的。
直接继承View的话,通常模板应该是这样的:
public class CircleView extends View {
.......省略若干代码........
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(500, 500);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(500, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, 500);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
//需要在这里设置padding哦
int radius = Math.min((width - getPaddingLeft() - getPaddingRight()) / 2,
(height - getPaddingTop() - getPaddingBottom()) / 2);
canvas.drawColor(Color.GRAY);
canvas.drawCircle(width / 2, height / 2, radius, mPaint);
}
}
直接继承viewGroup的话,模板大概是这样的:
public class TestViewGroup extends ViewGroup {
//使ViewGroup支持margin属性
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int width = 0;
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
width += childView.getMeasuredWidth() + params.rightMargin + params.leftMargin;
if (i == 0) {
height += childView.getMeasuredHeight() + params.topMargin + params.bottomMargin;
}
}
if (width > getScreenWidth()) {
setMeasuredDimension(width, height);
} else {
setMeasuredDimension((widthSpecMode == MeasureSpec.AT_MOST) ? width : widthSpecSize,
(heightSpecMode == MeasureSpec.AT_MOST) ? height : heightSpecSize);
}
}
//需要layout了,因为他要指定他的子view的拜访位置
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int left = 0;
View lastChildView = null;
for (int i = 0; i < getChildCount(); i++) {
View childView = getChildAt(i);
MarginLayoutParams params = (MarginLayoutParams) childView.getLayoutParams();
left += params.leftMargin;
if (lastChildView != null) {
left += lastChildView.getMeasuredWidth() + ((MarginLayoutParams) lastChildView.getLayoutParams()).rightMargin;
}
int right = left + childView.getMeasuredWidth();
int top = params.topMargin;
int bottom = childView.getMeasuredHeight() + top;
childView.layout(left, top, right, bottom);
lastChildView = childView;
}
}
然后对于第二大类,通常继承一个特定的view组件,是不需要写onMeasure
和onLayout
的,除非有必要,因为google的android工程师做的太牛逼了。
参考文章: