Android 绘制计时器

时间:2024-05-24 11:35:38

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

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

Android 绘制计时器

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

主页面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自带的绘图渲染来写很麻烦。所以可能是用第三方包,这里我就不写了(试着写过,但是渲染效果很差,就删除了)。

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