SurfaceView打造自定义时钟ClockView

来源:互联网 发布:lol国服有mac版 编辑:程序博客网 时间:2024/05/17 22:41

从事Android开发也一段时间了,一直有做云笔记的习惯,但是博客不怎么写。最近给自己定了个计划,坚持每周至少写三个自定义控件,所谓熟能生巧呀。作为第一篇写的博客,给大家带来用SurfaceView打造的自定义时钟。PS:最近看见洋神的一篇推送,有人写了这个,自己看着效果图就写了哈哈,至少效果感觉还阔以O(∩_∩)O


先看下这炫酷的效果哈哈(时钟上的横线是录制的原因,效果是没有那根线的,不影响哈):



先简单介绍一下surfaceView吧,surfaceView区别于普通的View,其拥有以下特点:

①拥有独立的绘图表面。

②surfaceView需要在宿主上挖一个洞来显示自己。

③其的UI绘制可以在独立的进程中进行,使用双缓冲机制,不会阻塞主线程的UI操作。


那么,喜欢刨根问底的朋友们又会有疑问了,到底什么是双缓冲机制?

在surfaceView里面,所谓的双缓冲机制其实就是说SurfaceView分front和back两块画布,这两块画布会相互交替来显示,每post一次交替一次,交替后显示在surfaceView上面。(这只是查阅资料之后的粗略理解),有兴趣的朋友可以自己深入研究哈O(∩_∩)O


那么为什么要使用双缓冲机制?

那是为了解决某种情况下(例如游戏场景)UI反复的局部刷新带来的闪烁问题。


好了,基本知识点已经介绍完毕,咱们开始撸码咯\(^o^)/~


首先,要使用surfaceView,一些基本的代码使有套路的。通常一个surfaceView的基本框架如下:

public class ClockSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {    private Canvas mCanvas;    private Paint mPaint;    private boolean isDrawing;    private SurfaceHolder mHolder;    public ClockSurfaceView(Context context) {        super(context);        init();    }    public ClockSurfaceView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    private void init() {        mHolder = getHolder();        mHolder.addCallback(this);        setFocusable(true);        setFocusableInTouchMode(true);        setKeepScreenOn(true);        mPaint = new Paint();        mPaint.setColor(Color.BLACK);        mPaint.setAntiAlias(true);        mPaint.setStyle(Paint.Style.STROKE);    }    @Override    public void surfaceCreated(SurfaceHolder holder) {        new Thread(this).start();    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    }    @Override    public void surfaceDestroyed(SurfaceHolder holder) {    }    private void draw() {        try {            mCanvas = mHolder.lockCanvas();            //draw sth        } catch (Exception e) {        } finally {            if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas);//这里确保每次都能提交        }    }    @Override    public void run() {        if (isDrawing) {            draw();        }    }}
说时迟,那时快,基本框架的代码已经撸完。毕竟作为LOL青铜段的大神,这手速,不谈了微笑

那么现在就可以开始画啦:

首先,我们先把时钟的圆盘画出来,代码很简单:

mCanvas.drawColor(Color.WHITE);//注意一定要刷屏mPaint.setColor(Color.BLACK);mCanvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, mPaint);

然后是画刻度和数字:

注意:这里画刻度和数字就需要捡回来高中的三角函数的数学了,作为曾经的数学课代表,so easy啦偷笑

弧度的图形表示:


求坐标点并修正坐标系的三角函数公式:
private float calculateX(int radius, double radian) {    return (float) (radius * Math.cos(radian)) + mCenterX;}private float calculateY(int radius, double radian) {    return -(float) (radius * Math.sin(radian)) + mCenterY;}

大体思想,我们先考虑,一个圆盘有60个小格,那么每个小格自然占的弧度是
public static final double= 2 * Math.PI / 60;

又由于我打算从12这个数字开始顺时针画一周,因此起始弧度为HALF_PI=Math.PI / 2;
需要注意的是,我们的整点的格子会有数字并且刻度线会加粗处理,因此,我们用currentPosition来记录当前所画的是第几个刻度,每5的整倍数刻度线就会加粗加长处理(好羞涩尴尬)
   /**     * 画刻度和数字     */    private void drawScaleAndNum() {        for (double i = HALF_PI; i > -1.5 * Math.PI; i -= intervalRadian) {            currentPosition += 1;            float outCircleX = calculateX(mRadius, i);            float outCircleY = calculateY(mRadius, i);//弧度的Y轴和Android的Y轴方向相反,因此取反            float inCircleX;//从圆盘上的点画起,确定终点的位置圆,从而画刻度线            float intCircleY;            if ((currentPosition - 1) / 5 < 12 && (currentPosition - 1) % 5 == 0) {//整点                inCircleX = calculateX(mRadius - DensityUtil.dip2px(mContext, 12.5f), i);                intCircleY = calculateY(mRadius - DensityUtil.dip2px(mContext, 12.5f), i);                int hourNum = (currentPosition - 1) / 5 == 0 ? 12 : (currentPosition - 1) / 5;                drawNum(mCanvas, hourNum + "", calculateX(mRadius - DensityUtil.dip2px(mContext, 30), i), calculateY(mRadius - DensityUtil.dip2px(mContext, 30), i));                mPaint.setStrokeWidth(4);//整点的刻度线加粗            } else {                inCircleX = calculateX(mRadius - DensityUtil.dip2px(mContext, 5), i);                intCircleY = calculateY(mRadius - DensityUtil.dip2px(mContext, 5), i);                mPaint.setStrokeWidth(2);            }            mCanvas.drawLine(outCircleX, outCircleY, inCircleX, intCircleY, mPaint);        }        currentPosition = 0;    }
画数字的方法:
private void drawNum(Canvas canvas, String text, float textX, float textY) {        mPaint.setTextSize(DensityUtil.dip2px(mContext, 12));        float textWidth = mPaint.measureText(text);        canvas.drawText(text, textX - textWidth / 2, textY + DensityUtil.dip2px(mContext, 4), mPaint);//绘制数字并修正数字的位置    }

然后是画时,分,秒针:
private void drawClockHand() {        mCanvas.drawLine(mCenterX, mCenterY, calculateX(SecondHandLength, secondRadian + HALF_PI), calculateY(SecondHandLength, secondRadian + HALF_PI), mPaint);        mPaint.setColor(Color.BLUE);        mCanvas.drawLine(mCenterX, mCenterY, calculateX(MinuteHandLength, secondRadian / 60 + HALF_PI), calculateY(MinuteHandLength, secondRadian / 60 + HALF_PI), mPaint);        mPaint.setColor(Color.RED);        //注意,这里除以的是720不是3600,因为时针走一小格为12分钟,相当于分针走12格,秒针走720格        mCanvas.drawLine(mCenterX, mCenterY, calculateX(hourHandLength, secondRadian / 720 + HALF_PI), calculateY(hourHandLength, secondRadian / 720 + HALF_PI), mPaint);    }


好啦,基本的轮廓已经画完:
我们让它跑起来吧:
在Run方法里面不断地更新当前时间:
/** * 更新当前时间和刻度值 */private void updateCurrentTime() {    mFormat.format(new Date());    mHour = mFormat.getCalendar().get(Calendar.HOUR_OF_DAY);    mMinute = mFormat.getCalendar().get(Calendar.MINUTE);    mSecond = mFormat.getCalendar().get(Calendar.SECOND);    int totalS = mHour * 3600 + mMinute * 60 + mSecond;    secondRadian = -totalS * intervalRadian;}


更新完之后,再进行绘制。并计算绘制用时,如果不足一秒,通过sleep让其睡眠,确保
我们每秒刷新一次。
@Overridepublic void run() {    while (mIsDrawing && sizeChanged) {        try {            updateCurrentTime();            long drawBefore = System.currentTimeMillis();            draw();            long drawTime = System.currentTimeMillis() - drawBefore;            if (drawTime < 1000) {                Thread.sleep(1000 - drawTime);            }        } catch (Exception e) {            e.printStackTrace();        }    }}
附上定义的成员变量:
    private SurfaceHolder mHolder;    private Canvas mCanvas;    private boolean mIsDrawing;    private Paint mPaint;    private int mRadius;    private int currentPosition = 0;//记录目前所画第几个刻度点,从3点开始逆时针    private int mCenterX;    private int mCenterY;    private int hourHandLength;    private int MinuteHandLength;    private int SecondHandLength;    private double secondRadian;    private boolean sizeChanged;    private int mMinute;    private int mSecond;    private int mHour;    private SimpleDateFormat mFormat;    public static final double intervalRadian = 2 * Math.PI / 60;    private static final double HALF_PI = Math.PI / 2;    private Context mContext;

至此,我们就大功告成啦。跑起来的效果就如开篇所示,有兴趣的朋友不妨试试O(∩_∩)O,第一次写博客,有不足的地方,欢迎指出交流学习大笑
附上项目源码地址:点击下载源码


0 0
原创粉丝点击