天天看點

Android計時器項目

本人是非計算機學院純零基礎,學了極客班的安卓微專業一個半月,準備将課程學習中做的作業進行一個整理,歡迎大家指正。

今天記錄的項目是一個類似于安卓原生計時器使用handler來更新UI。當然這種方法肯定不是計時器的正确編寫方法,在時間上會有很大誤差,隻是為了練習自定義控件和Handler的用法。

Android計時器項目

最後做出的結果如圖所示,在自定義控件中繪制了圓環作為進度條,在進度條内繪制數字來顯示時間。有開始、計次、複位三個按鈕。當按計次按鈕時在按鈕上方的listview中會記錄下目前記錄條數、目前記錄的總時間、目前記錄和上次記錄的間隔時間。

總體的思路就是繪制一個自定義控件,在activity中引用自定義控件,在activity中使用handler不斷發送資訊來更新自定義控件。

話不多說,直接上代碼,由于是初學者,是以代碼會注釋得十分詳細,也很适合初學者看。

public class ProgressBarByMyself extends View {
    //目前進度條進度
    private static float mProgress;
    //總進度
    private int mTotalProgress = ;
    //畫圓環背景的畫筆
    private Paint mCirclePaint;
    //畫圓環的畫筆
    private Paint mRingPaint;
    //畫字型的畫筆
    private Paint mTextPaint;
    //文字長度
    private float mTextWidth;
    //文字高度
    private float mTextHeight;
    //設定毫秒字型的畫筆、文字長度、文字高度
    private Paint mTestTextPaint;
    private float mTestTextWidth;
    private float mTestTextHeight;
    //圓環背景顔色
    private int mCircleColor;
    //圓環顔色
    private int mRingColor;
    //圓環半徑
    private float mRingRadius;
    //圓環寬度
    private float mStrokeWidth;
    //内圓半徑
    private float mRadius;
    //圓心X坐标
    private int mXCenter;
    //圓心Y坐标
    private int mYCenter;


    public ProgressBarByMyself(Context context) {
        this(context, null);
    }

    public ProgressBarByMyself(Context context, AttributeSet attrs) {
        this(context, attrs, );
        //擷取自定義屬性
    }

    public ProgressBarByMyself(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化各種屬性
        initAttrs(context, attrs);
        //擷取畫筆屬性
        initVariable();
    }

    private void initVariable() {
        //設定背景圓環的畫筆屬性
        //設定抗鋸齒屬性
        mCirclePaint.setAntiAlias(true);
        //設定顔色
        mCirclePaint.setColor(mCircleColor);
        //設定畫圖樣式為圓環
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setStrokeWidth(mStrokeWidth);

        //設定紅色活動進度條的畫筆屬性
        mRingPaint.setAntiAlias(true);
        mRingPaint.setColor(mRingColor);
        mRingPaint.setStyle(Paint.Style.STROKE);
        mRingPaint.setStrokeWidth(mStrokeWidth);

        //設定字型的畫筆屬性
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.FILL);
        //設定透明度和色彩,第一個參數是透明度,後三個參數是色彩
        mTextPaint.setARGB(, , , );
        //設定毫秒字型的畫筆屬性
        mTestTextPaint.setAntiAlias(true);
        mTestTextPaint.setStyle(Paint.Style.FILL);
        mTestTextPaint.setARGB(, , , );
    }


    private void initAttrs(Context context, AttributeSet attrs) {
        //建構畫筆執行個體
        mCirclePaint = new Paint();
        mRingPaint = new Paint();
        mTextPaint = new Paint();
        mTestTextPaint = new Paint();

        TypedArray typeArray = context.obtainStyledAttributes(attrs,R.styleable.ProgressBarByMyself);
        //設定圓環寬度
        mStrokeWidth = typeArray.getDimension(R.styleable.ProgressBarByMyself_strokeWidth, );
        //設定背景進度條的顔色
        mCircleColor=typeArray.getColor(R.styleable.ProgressBarByMyself_circleColor, );
        //設定紅色活動進度條的顔色
        mRingColor =typeArray.getColor(R.styleable.ProgressBarByMyself_ringColor, );
    }



    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //擷取各屬性的具體數字,mXcenter和mYcenter是圓環的圓心坐标。
        mXCenter = getWidth()/;
        mYCenter =getHeight()/;
        mRadius = getWidth()/;
        //設定字型的大小
        mTextPaint.setTextSize(mRadius*/);
        mTestTextPaint.setTextSize(mRadius/);
        mRingRadius = mRadius +mStrokeWidth/;
        //繪制出背景圓環
        canvas.drawCircle(mXCenter, mYCenter, mRingRadius, mCirclePaint);
        //擷取字型屬性
        Paint.FontMetrics fm = mTextPaint.getFontMetrics();
        mTextHeight = (int) Math.ceil(fm.descent - fm.ascent);
        Paint.FontMetrics lf = mTestTextPaint.getFontMetrics();
        mTestTextHeight = (int) Math.ceil(fm.descent - fm.ascent);
        //設定格式化顯示數字
        String text = String.format("%1$03d",((int)mProgress/));
        String testtext = String.format("%1$02d",(int)mProgress%);
        //設定字型寬度是剛好将自己放滿的寬度
        mTextWidth = mTextPaint.measureText(text, , text.length());
        mTestTextWidth = mTestTextPaint.measureText(testtext, , testtext.length());
        //計算外切矩形的點
        RectF Oval = new RectF();
        Oval.left = (mXCenter - mRingRadius);
        Oval.top=(mYCenter - mRingRadius);
        Oval.right = mRingRadius+mXCenter;
        Oval.bottom = mRingRadius+mYCenter;
        //畫出顯示秒和毫秒的數字以及紅色活動進度條
        canvas.drawText(text,mXCenter-mTextWidth*/,mYCenter+mTextHeight/,mTextPaint);
        canvas.drawText(testtext,mXCenter+mTextWidth/,mYCenter+mTestTextHeight/,mTestTextPaint);
        canvas.drawArc(Oval,-,(mProgress/mTotalProgress*),false,mRingPaint);
    }

    public static void setProgress(float progress) {
        mProgress = progress;
    }

    public static float getProgress(){
        float l=mProgress;
        return l;
    }
}
           

在自定義控件中主要畫出的就是計時器的圓形進度條和顯示的數字,其中的mProgress設定的是目前進度條的進度,在Activity中就是通過這一參數才對進度條進行更新,同樣的計次中listview中的資料也是通過擷取這個mProgress來計時的。

接着就上MainActivity的代碼。

public class HandlerActivity extends Activity implements View.OnClickListener {

    private ArrayList<TimeCount> timeCounts = new ArrayList<TimeCount>();
    //設定Message的辨識代号
    public static final int MESSAGE_CODE = ;
    //初始化Handler
    private TestHandler mTestHandler;
    //初始化按鈕
    private Button startButton;
    private Button timeCountButton;
    private Button resetButton;
    //初始化自定義控件
    public ProgressBarByMyself progressBarByMyself;
    //設定進度條狀态辨別,flag的真假顯示了目前是運作還是暫停
    private Boolean flag = true;
    //用于記錄目前是第幾次計次
    private int count = ;
    //初始化listView
    private ListView listView;
    private TimeAdapter adapter;
    private float countthistime=;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //通過id尋找按鈕和自定義控件
        progressBarByMyself = (ProgressBarByMyself) findViewById(R.id.my_progress_view);
        startButton = (Button) findViewById(R.id.start_button);
        timeCountButton = (Button) findViewById(R.id.time_count_button);
        resetButton = (Button) findViewById(R.id.reset_button);
        //為按鈕設定點選事件
        startButton.setOnClickListener(this);
        timeCountButton.setOnClickListener(this);
        resetButton.setOnClickListener(this);
        //為listview設定擴充卡
        adapter= new TimeAdapter(HandlerActivity.this,R.layout.time_countent,timeCounts);
        listView = (ListView) findViewById(R.id.listView);
        listView.setAdapter(adapter);
    }


    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.start_button:
                start();
                break;
            case R.id.time_count_button:
                count();
                break;
            case R.id.reset_button:
                reset();
                break;
        }
    }

    public void start(){
        if (flag) {
            //如果之前是暫停狀态,那麼擷取目前進度條的進度資訊然後繼續發送消息
            mTestHandler = new TestHandler(this);
            Message message = mTestHandler.obtainMessage();
            message.arg1 = ;
            message.arg2 = ;
            message.what = MESSAGE_CODE;
            message.obj = (int) progressBarByMyself.getProgress();
            mTestHandler.sendMessage(message);
            //當flag設定為false即開始狀态
            flag = false;
            startButton.setText("停止");
        } else {
            //如果之前是開始狀态,那麼現在将loop裡的消息全部清除,這樣就暫停了
            mTestHandler.removeMessages(MESSAGE_CODE);
            flag = true;
            startButton.setText("開始");
            }
    }

    //計時方法
    public void count() {
        //向listView的擴充卡傳入目前記錄是第幾條,目前經過總時間,和目前總之間與上次記錄的時間差
        TimeCount timeCount= new TimeCount(count,progressBarByMyself.getProgress()/,(progressBarByMyself.getProgress()-countthistime)/);
        //将目前記錄的總時間儲存起來,用來下次減的時候用
        countthistime=progressBarByMyself.getProgress();
        timeCounts.add(timeCount);
        //liseView裡加上變化的資料
        adapter.notifyDataSetChanged();
        //讓表示記錄個數的變量自增
        count += ;
    }

    //重置方法
    public void reset(){
        //移除loop内的Message
        mTestHandler.removeMessages(MESSAGE_CODE);
        //将進度條進度設定為零并重畫
        progressBarByMyself.setProgress();
        progressBarByMyself.invalidate();
        //将liseView和擴充卡都清零
        timeCounts.clear();
        adapter.clear();
        count=;
        countthistime = ;
        //将按鈕和文本都清零
        startButton.setText("開始");
        flag=true;
    }

    //建立TestHandler類
    public class TestHandler extends Handler {

        public WeakReference<HandlerActivity> mHandlerActivityWeakReference;

        public TestHandler(HandlerActivity activity) {
            mHandlerActivityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            HandlerActivity handlerActivity = mHandlerActivityWeakReference.get();
            // 接收消息
            switch (msg.what) {
                case MESSAGE_CODE:
                    int value = (int) msg.obj;
                    //将接受到的時間值設定為進度條的進度值并讓控件重畫
                    progressBarByMyself.setProgress(value);
                    progressBarByMyself.invalidate();
                    //更改Message資訊
                    msg = Message.obtain();
                    msg.arg1 = ;
                    msg.arg2 = ;
                    msg.what = MESSAGE_CODE;
                    msg.obj = value + ;
                    //每隔10ms發一次消息
                    sendMessageDelayed(msg, );
                    break;
            }
        }
    }
}
           

在主布局中,通過開始、計次和清零按鈕來對UI進行控制。

當點選開始按鈕時,Message消息開始每隔10ms發送一次,接受到消息之後就将消息裡的value值給mProgress,然後進行UI更新。

當計時器開始計時後,開始按鈕的文本就更換為暫停,點選暫停後,就會将MessageQueue裡的Message全部清空,這樣就不會繼續接受和發送了,進度條也就不動了。

當點選計次按鈕時,會将目前的次數count、進度條的進度mProgress、本次記錄的進度與上次記錄的進度相減這三個資訊傳入adapter,同時将目前進度記錄下來用來下次計算。為了讓最後的記錄始終能最先看到,listview裡要設定一個屬性android:stackFromBottom=”true”這樣每次listview更新後會自動滾動到最下方。

接下來貼上Adapter和計次類。

public class TimeCount {

    //初始化目前記錄條數、目前總時間、本次與上次記錄的時間差
    private int mNumber;
    private float time;
    private float alltime;
    private static float Alltime;

    //擷取到目前記錄條數、目前總時間、本次與上次記錄的時間差
    public TimeCount(int count, float progress, float v) {
        this.mNumber = count;
        this.alltime = progress;
        this.time = v;
    }

    public int getmNumber() {
        return mNumber;
    }

    public float getTime() {
        return time;
    }

    public float getAlltime() {
        return alltime;
    }


}
           
public class TimeAdapter extends ArrayAdapter<TimeCount> {

    private int resourceId;

    public TimeAdapter(Context context, int textViewResourceId, List<TimeCount> objects) {
        super(context,textViewResourceId, objects);
        resourceId = textViewResourceId;
    }


    @Override
    public View getView( int position,View convertView, ViewGroup parent) {
        //擷取位置資訊
        TimeCount timeCount = getItem(position);
        View view;
        view =  LayoutInflater.from(getContext()).inflate(resourceId, null);
        //計次參數
        TextView number= (TextView) view.findViewById(R.id.number);
        //計次時間間隔
        TextView time= (TextView) view.findViewById(R.id.time);
        //目前經過的總時間
        TextView alltime= (TextView) view.findViewById(R.id.all_time);
        //擷取到HandlerActivity傳入TimeCount裡的記錄條數、目前總時間、兩次記錄時間差得資訊
        number.setText(String.format("# %1$02d",timeCount.getmNumber()));
        time.setText(timeCount.getTime()+"");
        alltime.setText(timeCount.getAlltime()+"");
        return view;
    }
}
           

由于本人是新手,是以一些命名和格式可能做得不規範,歡迎大家指正。