录音计时的TimeView

本文深入解析如何通过继承View类实现计时功能的视图,包括构造函数、onMeasure方法、onDraw方法及关键尺寸计算。重点讨论了MeasureSpec模式与layout策略的关系,以及不同模式下尺寸的处理方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

介绍继承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);
  1. 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;  
} 
  1. 那么这些模式和fill_parent, wrap_content有什么关系呢?
    • fill_parent, MeasureSpec.getMode(widthMeasureSpec) 获得的是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。
    • wrap_content,获得的是AT_MOST, 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。
    • UNSPECIFIED模式目前还没有发现在什么情况下使用
  2. 不同的模式需要怎么处理呢?
    • 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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值