目录
3 onMeasure 测量 View 的大小并确定它们的宽度和高度
引言
自定义View核心考虑两件事情:View的大小(
onMeasure
)、View显示什么(
onDraw
)。
-
自定义属性
-
onDraw 绘制View
-
onMeasure 测量 View 的大小并确定它们的宽度和高度
1 自定义属性
在attr.xml中定义View的属性,内容如下:
<declare-styleable name="CustomView"> //View对应的属性
<attr name="custom_text" format="string" /> //属性名及类型
<attr name="custom_color" format="color|reference" /> //属性及类型
<attr name="custom_size" format="dimension" /> //属性及类型
</declare-styleable>
在java中如何使用自定义的属性值。
//获取declare-styleable标签
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
//获取declare-styleable标签下的attr
text = ta.getString(R.styleable.CustomView_custom_text);
textSize = ta.getDimension(R.styleable.CustomView_custom_size,12);
textColor = ta.getColor(R.styleable.CustomView_custom_color,0x000000);
//属性值recycle后方可使用
ta.recycle();
Q:自定义属性为何要使用属性配置?能不能在自定义View文件内使用set函数来设置?
A:当然可以使用set函数来做配置,只是不够灵活,无法在xml中配置;自定义View要呈现什么样的状态才是最佳的?自定义View的最高境界是犹如原生,所以使用属性配置会更好一些。
Q:
读取配置之后,为什么要recycle();
A:recycle的作用是释放内存
TypeArray是用来读取XML中定义的属性值的一个类,它用于在自定义视图或ViewGroup中读取自定义属性。
在使用完TypeArray之后,需要调用recycle()方法进行回收。原因是,TypeArray的生命周期不由垃圾回收机制掌控,而是由资源回收机制管理。因此,在使用完TypeArray时,需要显式地调用recycle()方法来释放资源,防止资源的浪费和内存泄漏。
同时,还需要注意,在回收TypeArray之后,不能再访问它的内容,否则会引发运行时异常。
2 onDraw 绘制View
在onDraw函数中绘制View,比如:绘制文本。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制文本
canvas.drawText(text,getPaddingLeft()+0,getPaddingTop()+mTextBound.height(),mPaint);
}
此方法实现在画布上绘制文本。
3 onMeasure 测量 View 的大小并确定它们的宽度和高度
onMeasure() 是重要的生命周期回调方法之一,主要作用是测量 View 的大小并确定它们的宽度和高度。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取 View 宽度的测量模式和大小
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int mWidth = 0;
int mHeight = 0;
if(specMode == MeasureSpec.EXACTLY){
//确定的大小或match_parent
mWidth = specWidth;
} else if(specMode == MeasureSpec.AT_MOST){
//wrap_content
mWidth = getPaddingLeft()+getPaddingRight()+mTextBound.width();
}
//获取 View 宽度的测量模式和大小
specMode = MeasureSpec.getMode(heightMeasureSpec);
int specHeight = MeasureSpec.getSize(heightMeasureSpec);
if(specMode == MeasureSpec.EXACTLY){
//确定的大小或match_parent
mHeight = specHeight;
} else if(specMode == MeasureSpec.AT_MOST){
//wrap_content
mHeight = getPaddingTop()+getPaddingBottom()+mTextBound.height();
}
//设置View的测量宽度和测量高度
setMeasuredDimension(mWidth,mHeight);
}
自定义View的宽度和高度模式分为2种,EXACTLY为固定的大小和match_parent,AT_MOST为wrap_content,需根据模式来计算View的宽度和高度。
注意:在计算宽度和高度时要加入padding值。
计算出View宽度和高度后,需要使用setMeasuredDimension设置测量的宽度和高度,方可生效。
如果不实现onMeasure方法,自定义View会使用父控件的宽度和高度。
4 完整代码
完整代码如下:
public class CustomView extends View {
private final String text; //文字内容
private final Paint mPaint;//画笔
private final Rect mTextBound; //文本边界
private final float textSize; //字体大小
private final int textColor; //字体颜色
public CustomView(Context context) {
this(context,null);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取declare-styleable标签
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
//获取declare-styleable标签下的attr
text = ta.getString(R.styleable.CustomView_custom_text);
textSize = ta.getDimension(R.styleable.CustomView_custom_size,12);
textColor = ta.getColor(R.styleable.CustomView_custom_color,0x000000);
//属性值recycle后方可使用
ta.recycle();
//画笔 设置颜色、样式、大小
mPaint = new Paint();
//文本边界,绘制文本需要的绘制的位置 left right top bottom
mTextBound = new Rect();
//设置大小
mPaint.setTextSize(textSize);
mPaint.setColor(textColor);
//获取绘制文本的位置
mPaint.getTextBounds(text,0,text.length(),mTextBound);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取 View 宽度的测量模式和大小
int specWidth = MeasureSpec.getSize(widthMeasureSpec);
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int mWidth = 0;
int mHeight = 0;
if(specMode == MeasureSpec.EXACTLY){
//确定的大小或match_parent
mWidth = specWidth;
} else if(specMode == MeasureSpec.AT_MOST){
//wrap_content
mWidth = getPaddingLeft()+getPaddingRight()+mTextBound.width();
}
//获取 View 宽度的测量模式和大小
specMode = MeasureSpec.getMode(heightMeasureSpec);
int specHeight = MeasureSpec.getSize(heightMeasureSpec);
if(specMode == MeasureSpec.EXACTLY){
//确定的大小或match_parent
mHeight = specHeight;
} else if(specMode == MeasureSpec.AT_MOST){
//wrap_content
mHeight = getPaddingTop()+getPaddingBottom()+mTextBound.height();
}
//设置View的测量宽度和测量高度
setMeasuredDimension(mWidth,mHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制文本
canvas.drawText(text,getPaddingLeft()+0,getPaddingTop()+mTextBound.height(),mPaint);
}
}
在绘制文本时,首先需要文本的内容,然后需要获取文本的边界,即使用Rect获取View的left、right、top、bootom当可确定View的大小,Android 的界面使用坐标系来布局,所以对于界面内的控件在绘制时要使用坐标点来定位和确定大小。
5 绘制流程
视图是先测量onMeasure后绘制onDraw。
6 总结
此文的目的是为了记录自定义View的核心内容,至于自定义View绘制的具体实现由实际需求决定,绘制api如何用等可参考官方控件和源码中对控件的介绍,如文本类的控件可参考TextView。