录音计时的TimeView

介绍继承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();
当你在Android Studio中创建一个新项目时,你可以在Activity中编写计时器代码。以下是一个简单的计时器示例,它可以启动、停止和重置计时器: ```java public class MainActivity extends AppCompatActivity { private int seconds = 0; private boolean running; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (savedInstanceState != null) { seconds = savedInstanceState.getInt("seconds"); running = savedInstanceState.getBoolean("running"); } runTimer(); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); savedInstanceState.putInt("seconds", seconds); savedInstanceState.putBoolean("running", running); } public void onClickStart(View view) { running = true; } public void onClickStop(View view) { running = false; } public void onClickReset(View view) { running = false; seconds = 0; } private void runTimer() { final TextView timeView = (TextView)findViewById(R.id.time_view); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { int hours = seconds / 3600; int minutes = (seconds % 3600) / 60; int secs = seconds % 60; String time = String.format("%d:%02d:%02d", hours, minutes, secs); timeView.setText(time); if (running) { seconds++; } handler.postDelayed(this, 1000); } }); } } ``` 以上代码定义了一个`MainActivity`类,该类扩展了`AppCompatActivity`类。计时器的时间以秒为单位存储在`seconds`变量中,计时器的运行状态存储在`running`变量中。 当创建Activity时,它会尝试从之前保存的实例状态中恢复计时器的时间和运行状态。如果没有保存实例状态,则默认为0秒并停止运行。 计时器的UI由一个`TextView`控件显示。`runTimer()`方法使用一个`Handler`对象和`postDelayed()`方法更新计时器UI和计时器变量。当用户点击开始、停止或重置按钮时,相应的方法将设置运行状态和/或计时器变量的值。 如果您有任何其他问题,请随时问我!下面是几个相关的问题:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值