Android 绘制计时器

 用小米的手机,发现其实还可以,无意间点开小米的计时器,发现界面非常好看和实用。于是自己仿照着写一个,由于技术不好,代码整体结构上可能有点乱,但主要的实现功能和掌握知识点。

  Android中绘制采用canvase绘图类,加上timer计时器和handler来更新UI,核心就这点东西,非常简单。废话不多说,先附上效果图。

和小米自带的还是有差距的,主要是运行的时候指针的转动感觉没小米的那么流畅。这一点还在优化中。直接上代码,所有的注视都写上了,没什么好说的。

主页面xml文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#E0EEEE"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <LinearLayout
        android:id="@+id/main_view"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_weight="5"
        android:orientation="horizontal" >
    </LinearLayout>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#FFFFFF" />

    <ListView
        android:id="@+id/main_listview"
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:layout_weight="2" >
    </ListView>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#FFFFFF" />

    <Button
        android:id="@+id/main_button_start"
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_weight="1"
        android:text="开始" />

    <LinearLayout
        android:id="@+id/main_lin"
        android:layout_width="match_parent"
        android:layout_height="10dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_weight="1"
        android:orientation="horizontal"
        android:visibility="gone" >

        <Button
            android:id="@+id/main_button_zanting"
            android:layout_width="50dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="暂停" />

        <Button
            android:id="@+id/main_button_jici"
            android:layout_width="50dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:text="记次" />
    </LinearLayout>

</LinearLayout>
XMl

 

辅助类Times,也就是时间类,实例化即可用,然后再tostring上做了格式输出

package com.example.timer;

import java.text.Format;

public class Times {

    public int getHour() {
        return Hour;
    }

    @Override
    public String toString() {
        return  String.format("%02d",Hour)+":"+String.format("%02d",minute)+":"+String.format("%.1f",second);
    }

    public Times(int hour, int minute, float second) {
        super();
        Hour = hour;
        this.minute = minute;
        this.second = second;
    }

    public void setHour(int hour) {
        Hour = hour;
    }

    public int getMinute() {
        return minute;
    }

    public void setMinute(int minute) {
        this.minute = minute;
    }

    public float getSecond() {
        return second;
    }

    public void setSecond(float second) {
        this.second = second;
    }

    private int Hour;
    private int minute;
    private float second;

    public void secondAdd() {
        if (second > 59) {
            second = 0;
            if (minute > 59) {
                minute = 0;
                Hour++;
            } else {
                minute++;
            }
        } else {
            second += 0.1;
        }
    }
}
Times

接下来是最主要的绘图代码,MyCanvases继承View,绘制圆,指针刻度等等。其中绘制指针比较复杂,但是目前没有想到什么好的方法,大家如果有,请给我分享下。指针主要是画出一个封闭的三角形,为了保持转动,使用自己写的arithmetic1一个小的算法,因为要用到圆和三角函数来计算坐标(忘了差不多了,还把数学几何图形复习了一遍......累死了。细心的朋友可能发现,Math.sin方法参数是弧度,而我显示把角度计算出来,再换算成弧度,绕了一大圈。不要喷我,当时为了这些几何图形,手头又没笔,都把我写晕了,这部分代码真心不想动了。)附上代码

package com.example.timer;

import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathEffect;
import android.os.Handler;
import android.os.Message;
import android.view.View;

public class MyCanvases extends View {
    private Paint paint, paint2, paint3, paint4, paint5, paint6, paint7;
    private Timer timer;
    private Handler handler;
    private Canvas canvas;
    private float X = 50, Y = 20, W = 0, H = 0;
    private Times times = new Times(0, 0, 0);
    private Path path;
    Message message;
    MyTimer myTimer;

    public MyCanvases(Context context, float x, float y) {
        super(context);
        message = Message.obtain();
        handler = new MyHandler();

        this.W = x;
        this.H = y;
        paint = new Paint();
        // 去锯齿
        paint.setAntiAlias(true);
        paint.setColor(Color.BLACK);
        // 画笔样式
        paint.setStyle(Paint.Style.STROKE);
        // 画笔粗细
        paint.setStrokeWidth(1);

        paint3 = new Paint();
        paint3.setAntiAlias(true);
        paint3.setColor(Color.GRAY);
        paint3.setStyle(Paint.Style.STROKE);
        paint3.setStrokeWidth(35);

        paint2 = new Paint();
        paint2.setAntiAlias(true);
        paint2.setColor(Color.BLACK);
        paint2.setStyle(Paint.Style.FILL);
        paint2.setTextSize(53);

        paint4 = new Paint();
        paint4.setAntiAlias(true);
        paint4.setColor(Color.rgb(224, 238, 238));
        paint4.setStyle(Paint.Style.FILL_AND_STROKE);
        paint4.setStrokeWidth(10);

        paint5 = new Paint();
        paint5.setAntiAlias(true);
        paint5.setColor(Color.YELLOW);
        paint5.setStyle(Paint.Style.STROKE);
        paint5.setStrokeWidth(30);

        paint6 = new Paint();
        paint6.setAntiAlias(true);
        paint6.setColor(Color.GRAY);
        PathEffect pathEffect = new DashPathEffect(new float[] { 5, 5 }, 1);
        paint6.setPathEffect(pathEffect);
        paint6.setStyle(Paint.Style.STROKE);
        paint6.setStrokeWidth(12);

        paint7 = new Paint();
        paint7.setAntiAlias(true);
        paint7.setColor(Color.GRAY);
        paint7.setStyle(Paint.Style.FILL);
        paint7.setStrokeWidth(1);
    }

    protected void onDraw(Canvas canvas) {
        this.canvas = canvas;
        // 内外圆
        canvas.drawCircle(W / 2, H / 2 - 50, W / 2 - 100, paint);
        canvas.drawCircle(W / 2, H / 2 - 50, W / 2 - 150, paint3);
        // 时间
        canvas.drawText(times.toString(), W / 2 - 120, H / 2 - 50, paint2);
        // 指针
        // 这个指针其实是一个三角形,外圆上一点加上内圆上两点,构成一个闭合三脚形。时间变化时3个点一起移动,看起来就是一个指针的效果了
        float a[] = arithmetic1(W / 2, H / 2 - 50, W / 2 - 137,
                times.getSecond());
        float b[] = arithmetic1(W / 2, H / 2 - 50, W / 2 - 180,
                times.getSecond() - 2);
        float c[] = arithmetic1(W / 2, H / 2 - 50, W / 2 - 180,
                times.getSecond() + 2);
        path = new Path();
        path.moveTo(a[0], a[1]);
        path.lineTo(b[0], b[1]);
        path.lineTo(c[0], c[1]);
        path.close();
        canvas.drawPath(path, paint4);
        // 小圆
        canvas.drawCircle(W - 150, H - 150, 80, paint);
        // 刻度
        canvas.drawCircle(W - 150, H - 150, 70, paint6);
        // 指针

        canvas.drawCircle(W - 150, H - 150, 10, paint7);

        a = arithmetic1(W - 150, H - 150, 60, times.getSecond() * 60);
        b = arithmetic1(W - 150, H - 150, 10, times.getSecond() * 60 - 4);
        c = arithmetic1(W - 150, H - 150, 10, times.getSecond() * 60 + 4);
        path = new Path();
        path.moveTo(a[0], a[1]);
        path.lineTo(b[0], b[1]);
        path.lineTo(c[0], c[1]);
        path.close();
        canvas.drawPath(path, paint7);
    }

    // 计时器
    public class MyTimer extends TimerTask {
        public void run() {
            handler.sendMessage(new Message().obtain());
        }
    }

    public class MyHandler extends Handler {
        public void handleMessage(Message msg) {
            times.secondAdd();
            invalidate();

        }
    }

    /**
     * 自定义方法,以60秒为圆的一圈,给出圆的参数,获得对应时间在圆周上点的坐标。所以不同时间可以通用,只需要根据秒对比相应的直即可。(
     * 例如传入的time为分钟,将time/60就是分钟的坐标变化)
     * 
     * @param x
     *            圆心x坐标
     * @param y
     *            圆心y坐标
     * @param r
     *            圆的半径
     * @param time
     *            时间
     * @return 返回一个float数组,大小为2。float0为x坐标,float1为y坐标;
     */
    public float[] arithmetic1(float x, float y, float r, float time) {
        float circle[] = new float[2];
        float angle;
        angle = 90 - time * 6;
        circle[0] = (float) (Math.cos(angle * Math.PI / 180) * r + x);
        circle[1] = (float) (y - Math.sin(angle * Math.PI / 180) * r);
        return circle;
    }

    /**
     * 启动计时器
     */
    public void Start() {
        timer = new Timer();
        myTimer = new MyTimer();
        timer.schedule(myTimer, 0, 100);

    }

    /**
     * 停止计时器
     */
    public void Stop() {
        timer.cancel();
        myTimer.cancel();
    }

    /**
     * 获取这一刻的时间
     * 
     * @return
     */
    public String getTime() {
        return times.toString();
    }

    /**
     * 这个方法其实就是重置时间
     * 
     */
    public void setTime() {
        times = new Times(0, 0, 0);
        invalidate();
    }
}
MyCanvases

最后是主页面activity代码,其中在listview上没有写布局,就用最简单的数组资源做适配器,因为这个listview只有一个显示的功能,所以就没用多纠结。大家可以自己改改喜欢的布局加上去。

package com.example.timer;

import java.util.ArrayList;
import java.util.List;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ListView;

public class MainActivity extends Activity {
    private ListView listview;
    private Button btnStart, btnJici, btnStop;
    private LinearLayout lin, linView;
    MyCanvases myCanvases;
    List<String> list;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        list = new ArrayList<String>();
        intview();
    }

    /**
     * 获得控件关联,为按钮设置监听
     */
    private void intview() {
        linView = (LinearLayout) findViewById(R.id.main_view);
        listview = (ListView) findViewById(R.id.main_listview);
        btnStart = (Button) findViewById(R.id.main_button_start);
        btnStart.setOnClickListener(new MyOnClick());
        btnJici = (Button) findViewById(R.id.main_button_jici);
        btnJici.setOnClickListener(new MyOnClick());
        btnStop = (Button) findViewById(R.id.main_button_zanting);
        btnStop.setOnClickListener(new MyOnClick());
        lin = (LinearLayout) findViewById(R.id.main_lin);
    }

    /**
     * 实现点击事件接口,用控件id来区分不同事件,将所有事件集中到一起处理。(个人比较喜欢这样写)
     * 
     * @author
     * 
     */
    public class MyOnClick implements OnClickListener {

        public void onClick(View v) {
            switch (v.getId()) {
            case R.id.main_button_start:
                myCanvases.Start();
                btnStart.setVisibility(View.GONE);
                lin.setVisibility(View.VISIBLE);
                break;
            case R.id.main_button_zanting:
                if (btnStop.getText().toString().equals("暂停")) {
                    myCanvases.Stop();
                    btnStop.setText("继续");
                    btnJici.setText("重置");
                } else {
                    myCanvases.Start();
                    btnStop.setText("暂停");
                    btnJici.setText("记次");
                }
                break;
            case R.id.main_button_jici:
                if (btnJici.getText().toString().equals("重置")) {
                    myCanvases.setTime();
                    btnStart.setVisibility(View.VISIBLE);
                    lin.setVisibility(View.GONE);
                    btnStop.setText("暂停");
                    btnJici.setText("记次");
                    // 清空数据
                    list.clear();
                    // 清空listview
                    listview.setAdapter(null);
                } else {
                    // 添加数据
                    list.add(myCanvases.getTime());
                    String[] text = new String[list.size()];
                    for (int i = 0; i < list.size(); i++) {
                        text[i] = "第" + (i + 1) + "次:               "
                                + list.get(i);
                    }
                    // 建立适配器
                    ArrayAdapter<String> adapter = new ArrayAdapter<String>(
                            MainActivity.this,
                            android.R.layout.simple_list_item_1, text);
                    // 添加适配器
                    listview.setAdapter(adapter);
                    // 将listview的焦点始终锁定在最后一行,很有用
                    listview.setSelection(adapter.getCount());
                }
            default:
                break;
            }
        }
    }

    // 重写activit的onWindowFocusChanged方法,是为了在布局加载完成之后,
    // 才能获得到控件的大小和位置,将参数传递到绘图类
    public void onWindowFocusChanged(boolean hasFocus) {
        if (hasFocus) {
            myCanvases = new MyCanvases(this, linView.getWidth(),
                    linView.getHeight());
            //将绘制的图像加载到对应的布局上显示
            linView.addView(myCanvases);
        }
    }
}
MainActivity

 

  最后说一些细节。在绘制的坐标问题上,我先是在activity布局加载完成之后,将绘制区域的宽度和高度获取到,根据这个数据来绘制,本来是想实现在不同手机屏幕实现自适应效果,后来发现。手机分辨率不一样的时候,还是没能实现,所以大家根据自己的需要改改坐标。至于怎么实现自适应屏幕,由于没有系统的学习过,现在还是没有解决好。我会努力的。还有就是小米自带计时器上有渲染的效果,就是指针转动时颜色会发生变化,这个用android自带的绘图渲染来写很麻烦。所以可能是用第三方包,这里我就不写了(试着写过,但是渲染效果很差,就删除了)。

   大家有什么意见,欢迎随时交流。大神勿喷....

posted @ 2015-08-05 12:32  yangbo——  阅读(981)  评论(0编辑  收藏  举报