自定义控件免不了图形绘制,每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()测量、onLayout()布局、onDraw()绘制 三个阶段,今天主要将第一个阶段测量。关于三个阶段的协作请看:Android视图绘制流程——郭霖
常见对于 文字 以及 图片 两种的测量,图片的测量一般情况比较简单不多说,主要先说一下文字的测量。
常见对于图片的一些常见的绘制图形,直接从左上角的(0,0)开始绘制。但对于文字如果你以相同的方式进行绘制会出现一些问题,文字并不能合理的展示出来。如:使用如下代码进行文字绘制:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Paint.ANTI_ALIAS_FLAG 抗锯齿
// Paint.DEV_KERN_TEXT_FLAG 优化文字细节 使大小均匀 对齐
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
paint.setTextSize(50);
paint.setColor(Color.RED);
canvas.drawText("hello world!jjj",0,0,paint);
}
效果:
如图所示,hello world几乎只有很少的一点,jjj 只能够能够看下面的部分。
请允许我在网上找了一张图,感谢作者!a men!
如上图,在canvas.drawText()方法中,所传入的 Y 值,不是如同图片那样是最左上角的点,而是如图所示的Baseline的位置。
这里使用的是如下方式获取文字的宽度与高度:
// 获取测量矩形
Rect rect = new Rect();
paint.getTextBounds(text,0,text.length(),rect);
// 高度 宽度
height = rect.bottom - rect.top;
width = rect.right - rect.left;
canvas.drawText(text,-rect.left,-rect.top,paint);
但是上面的宽高只是在获取文字所占的宽高,由于是以基线为标准,此时绘制文字应该用上面的代码,结果如图:
此时就能将文字全部展示出来了,文字的测量部分也到此结束。
将文字的宽高,以及图片的宽高测量出来之后就可以测量整个控件所需要的不同状态的大小了。本次目标自定义的控件如下图:
绘制这个控件就需要将一个图片与一个文字的组合进行测量。将代码贴出来先:
/**
* 测量方式
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = mImgWidth * 2 + 30; // 控件最小宽度
int desiredHeight = mImgHeight + dip2px(getContext(), 10) + mTextHeight; // 控件最小高度
int widthMode = MeasureSpec.getMode(widthMeasureSpec); //取出宽度的测量模式
int widthSize = MeasureSpec.getSize(widthMeasureSpec); //取出宽度的确切数值
int heightMode = MeasureSpec.getMode(heightMeasureSpec); //取出高度的测量模式
int heightSize = MeasureSpec.getSize(heightMeasureSpec); //取出高度的确切数值
// 实际宽高
int width;
int height;
//Measure Width 测量宽度
if (widthMode == MeasureSpec.EXACTLY) { // 父控件已确定的指定子View的大小
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) { // 没有大小限制,存在上限一般为父控件大小
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else { // 默认值,父控件没有给子控件任何限制
//Be whatever you want
width = desiredWidth;
}
//Measure Height 测量高度
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
测量时会出现很多种不同的宽高,这里一一分析。
最小宽高:即是完整展示控件不缺少图像的最小情况所需要的空间大小。这里最小的情况可以对宽度进行压缩,将图片进行重叠绘制。
测量模式:确定测量的是三种模式的中的哪一种。
测量的确切宽高:父控件给出的宽高大小。
根据测量的模式来获取确定的控件宽高值,测量模式有三种模式:
MeasureSpec.EXACTLY:指定大小,即 xml 中直接指定 xxdp 和 match_parent;
MeasureSpec.AT_MOST:自适应大小,即 xml 中的 wrap_content,此时测量尺寸为父控件尺寸。
UNSPECIFIED:不限制大小,通常是AdapterView 中的 item 中的高度。
个人使用了上面的方式进行测量,别忘了最后调用 setMeasureDimension(宽,高)方法,将测量所得的控件最终大小进行设置。测量阶段到此结束,最后分享一下如何将屏幕适配的 dp 转换成系统识别的 px。
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
最后跟上这个Demo的GitHub地址:GitHub简单自定义View Demo地址