介绍继承View实现计时功能的视图
继承View的子类必须有自己的构造函数
public class TimeView extends View
{
public TimeView(Context context)
{
super(context);
//初始化资源
init(context);
}
public TimeView(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
}
TimeView更新重绘需要override两个方法, onMeasure 和 onDraw
先介绍onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
//widthMeasureSpec,heightMeasureSpec是父类传递的参数,
//结合在XML定义的长宽调整我们的view显示的大小
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
}
参数详解:
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
MeasureSpec有3种模式分别是UNSPECIFIED, EXACTLY和AT_MOST)
- AT_MOST 代表 最大可获得的空间
- EXACTLY 代表 精确的尺寸
- UNSPECIFIED,对于控件尺寸来说,没有任何参考意义
private int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
- 那么这些模式和fill_parent, wrap_content有什么关系呢?
- fill_parent, MeasureSpec.getMode(widthMeasureSpec) 获得的是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。
- wrap_content,获得的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。
- UNSPECIFIED模式目前还没有发现在什么情况下使用
- 不同的模式需要怎么处理呢?
- UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸
- 当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。
- 需要注意的是,fill_parent应该是子view会占据剩下容器的空间,而不会覆盖前面已布局好的其他view空间,当然后面布局子 view就没有空间给分配了,所以fill_parent属性对布局顺序很重要。以前所想的是把所有容器的空间都占满了,
- google在2.2版本里把fill_parent的名字改为match_parent。现在两者都可用。
看代码的处理
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//不处理MeasureSpec.UNSPECIFIED
if (widthMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.UNSPECIFIED)
{
throw new RuntimeException("TimeView cannot have UNSPECIFIED dimensions");
}
calcPosition();
int width = 0, height = 0;
//MeasureSpec.EXACTLY 直接使用父类给的尺寸
if (widthMode == MeasureSpec.EXACTLY)
{
width = widthSize;
}
//MeasureSpec.AT_MOST 调整显示的大小,mDigitWidth为图片本身的大小
else if (widthMode == MeasureSpec.AT_MOST)
{
if (mHour == 0)
{
width = 4 * mDigitWidth + 4 * mSpace + mColonWidth + getPaddingLeft() + getPaddingRight();
}
else
{
width = 6 * mDigitWidth + 7 * mSpace + 2 * mColonWidth + getPaddingLeft() + getPaddingRight();
}
}
if (heightMode == MeasureSpec.EXACTLY)
{
height = heightSize;
}
else if (heightMode == MeasureSpec.AT_MOST)
{
height = mDigitHeight + getPaddingTop() + getPaddingBottom() + (mPaddingBottom * 2);
}
//最后一定要调用此方法,不然会出错
setMeasuredDimension(width, height);
获取Drawable图片尺寸的方法
private void loadResources(Context context)
{
if (context == null)
{
return;
}
Resources res = context.getResources();
mDigits[0] = res.getDrawable(R.drawable.fmradio_digit_0);
mColon = res.getDrawable(R.drawable.ringtone_trimmer_digit_colon);
mDigitOriginalWidth = mDigits[0].getIntrinsicWidth();
mDigitOriginalHeight = mDigits[0].getIntrinsicHeight();
mColonOriginalWidth = mColon.getIntrinsicWidth();
mColonOriginalHeight = mColon.getIntrinsicHeight();
mDigitWidth = mDigitOriginalWidth;
mDigitHeight = mDigitOriginalHeight;
mColonWidth = mColonOriginalWidth;
mColonHeight = mColonOriginalHeight;
mSpace = (int)this.getResources().getDimension(R.dimen.margin_m);
}
onDraw()的实现
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (getMeasuredWidth() == 0 || getMeasuredHeight() == 0)
return;
try
{
mDrawStartX = getPaddingLeft();
if (mHour == 0)
{
//处理图片,并绘制
draw(mDigits[mMin / 10], true, canvas);
}
else
{
}
}
catch (Exception e)
{
VRLog.e(TAG, getTextPos());
e.printStackTrace();
}
}
draw方法
private void draw(Drawable drawable, boolean isDigit, Canvas canvas)
{
if (drawable == null)
return;
int bottom = getMeasuredHeight() - getPaddingBottom() - mPaddingBottom;
if (isDigit)
{
drawable.setBounds(mDrawStartX, bottom - mDigitHeight, mDrawStartX + mDigitWidth, bottom);
mDrawStartX += mDigitWidth;
}
else
{
int colonBottom = bottom + (mDigitHeight - mColonHeight) / 2;
drawable.setBounds(mDrawStartX, colonBottom - mColonHeight, mDrawStartX + mColonWidth, colonBottom);
mDrawStartX += mColonWidth;
}
mDrawStartX += mSpace;
drawable.setAlpha(mAlpha);
//绘制
drawable.draw(canvas);
}
TimeView写好,需要刷新的时候,在其他类中需要刷新的时候,如下使用:
TimerView.requestLayout();
在自己类中,使用
invalidate();