首页 > 代码库 > Android自定义一个属于自己的时间钟表

Android自定义一个属于自己的时间钟表

1、概述

本文主要讲解的是如何自定义一个时间钟表,通过简单的练习可以简单学习android当中自定义view的一些常用绘图技巧,优化android绘图操作。言归正传,首先看下我们需要实现的效果:

技术分享


当我们看到这个效果的时候脑子里应该有一定的思路了,我们应该把它分解成以下几个步骤:

1、仪表盘(圆)

2、刻度线(长 中 短)

3、刻度值(1-12)

4、指针(时  分  秒)

5、移动指针,计算指针位置

现在我们已经很清楚自己的思路了,那么我们一个一个来。


第一步:1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

    <declare-styleable name="ClockView">

        <attr name="mRadius" format="dimension"/>
        <attr name="mCircleColor" format="color"/>
        <attr name="mCircleWidth" format="dimension"/>
        <attr name="mTextSize" format="dimension"/>
        <attr name="mTextColor" format="color"/>
        <attr name="mBigScaleColor" format="color"/>
        <attr name="mMiddlecaleColor" format="color"/>
        <attr name="mSmallScaleColor" format="color"/>
        <attr name="mHourHandColor" format="color"/>
        <attr name="mMinuteHandColor" format="color"/>
        <attr name="mSecondHandColor" format="color"/>
        <attr name="mHourHandWidth" format="dimension"/>
        <attr name="mMinuteHandWidth" format="dimension"/>
        <attr name="mSecondHandWidth" format="dimension"/>

    </declare-styleable>

我们定义了钟表的半径,背景颜色 ,刻度值(大,中,小)的颜色及指针(时分秒)的颜色和宽度。


然后自定义一个class类 为ClockView,在MainActivity的布局中引用:

  <com.dalong.customviewstudy.view.ClockView
      app:mSecondHandColor="@color/colorAccent"
      app:mCircleColor="@android:color/white"
      app:mBigScaleColor="@android:color/black"
      app:mMiddlecaleColor="@android:color/black"
      app:mSmallScaleColor="@color/colorAccent"
      app:mHourHandColor="@android:color/black"
      app:mMinuteHandColor="@android:color/black"
      app:mTextColor="@android:color/black"
      app:mHourHandWidth="13dp"
      app:mSecondHandWidth="5dp"
      app:mMinuteHandWidth="8dp"
      app:mTextSize="16sp"
      android:layout_centerInParent="true"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />

2、在自定义View的构造方法中,获得我们的自定义的样式


 //文字画笔对象
    private  Paint mTextPaint;

    //圆,指针,刻度画笔
    private  Paint mPaint;

    //半径
    public float mRadius;

    //外圆的颜色
    public int mCircleColor;

    // 外圆的宽度
    public float mCircleWidth;

    //文字的大小
    public float mTextSize;

    //文字的颜色
    public int mTextColor;

    //大刻度颜色
    public int mBigScaleColor;

    //中刻度
    public int mMiddlecaleColor;

    //小刻度颜色
    public int mSmallScaleColor;

    //时针颜色
    public int mHourHandColor;

    //分针颜色
    public int mMinuteHandColor;

    //秒针颜色
    public int mSecondHandColor;

    //时针宽度
    public float mHourHandWidth;

    //分针宽度
    public float mMinuteHandWidth;

    //秒针宽度
    public float mSecondHandWidth;

    //控件宽度
    public int mWidth;

    //控件高度
    public int mHeght;

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

    public ClockView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.ClockView);
        mRadius=typedArray.getDimension(R.styleable.ClockView_mRadius,400);
        mCircleColor=typedArray.getColor(R.styleable.ClockView_mCircleColor, Color.WHITE);
        mCircleWidth=typedArray.getDimension(R.styleable.ClockView_mCircleWidth,20);
        mTextSize=typedArray.getDimension(R.styleable.ClockView_mCircleWidth,40);
        mTextColor=typedArray.getColor(R.styleable.ClockView_mTextColor,Color.DKGRAY);
        mBigScaleColor=typedArray.getColor(R.styleable.ClockView_mBigScaleColor,Color.BLACK);
        mSmallScaleColor=typedArray.getColor(R.styleable.ClockView_mSmallScaleColor,Color.RED);
        mMiddlecaleColor=typedArray.getColor(R.styleable.ClockView_mMiddlecaleColor,Color.BLACK);
        mHourHandColor=typedArray.getColor(R.styleable.ClockView_mHourHandColor,Color.BLACK);
        mMinuteHandColor=typedArray.getColor(R.styleable.ClockView_mMinuteHandColor,Color.BLACK);
        mSecondHandColor=typedArray.getColor(R.styleable.ClockView_mSecondHandColor,Color.BLACK);
        mHourHandWidth=typedArray.getDimension(R.styleable.ClockView_mHourHandWidth,20);
        mMinuteHandWidth=typedArray.getDimension(R.styleable.ClockView_mMinuteHandWidth,10);
        mSecondHandWidth=typedArray.getDimension(R.styleable.ClockView_mSecondHandWidth,5);

        mPaint=new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);

        mTextPaint=new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);

    }

3、我们重写onDraw,onMesure调用系统提供的:


onMeure方法

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSize(widthMeasureSpec),measureSize(heightMeasureSpec));
    }

    private int measureSize(int mMeasureSpec) {
        int result;
        int mode=MeasureSpec.getMode(mMeasureSpec);
        int size=MeasureSpec.getSize(mMeasureSpec);
        if(mode==MeasureSpec.EXACTLY){
            result=size;
        }else{
            result=400;
            if(mode==MeasureSpec.AT_MOST){
                result=Math.min(result,size);
            }
        }
        return  result;
    }

onDraw方法


 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置宽高、半径
        mWidth=getMeasuredWidth()-getPaddingLeft()-getPaddingRight();
        mHeght=getMeasuredHeight()-getPaddingBottom()-getPaddingTop();
        mRadius=Math.min(mWidth/2,mHeght/2);
        //首先绘制圆
        drawCircle(canvas);
        //绘制刻度
        drawScale(canvas);
        //绘制指针
        drawPointer(canvas);
        //发送消息刷新ui
        handler.sendEmptyMessageDelayed(START_CLOCK,1000);
    }

其中最核心的代码就是这三个方法:

       //首先绘制圆
        drawCircle(canvas);
        //绘制刻度
        drawScale(canvas);
        //绘制指针
        drawPointer(canvas);

首先讲第一个方法:

 /**
     * 画圆
     * @param canvas
     */
    private void drawCircle(Canvas canvas) {
        mPaint.setStrokeWidth(mCircleWidth);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mCircleColor);
        canvas.drawCircle(mWidth/2,mHeght/2,mRadius,mPaint);
    }

这个方法其实很简单给我们的画笔设置我们自定义的样式之后取中心为圆心,以我们设定的半径画圆,这里设置的是Paint.Style.FILL一个实心的。也可以设置一个空心的。看下我们执行这个方法后的效果:

技术分享

第二方法:

 /**
     * 刻度和文字
     * @param canvas
     */
    private void drawScale(Canvas canvas) {

        for (int i=0;i<60;i++){
            //设置大刻度
            if(i==0||i==15||i==30||i==45){
                mPaint.setStrokeWidth(6);
                mPaint.setColor(mBigScaleColor);
                canvas.drawLine(mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2,
                        mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2+60,mPaint);
                String scaleTv=String.valueOf(i==0?12:i/5);
                canvas.drawText(scaleTv,mWidth/2-mTextPaint.measureText(scaleTv)/2,
                        mHeght/2-mWidth/2+mCircleWidth/2+95,mTextPaint);
            }else if (i==5||i==10||i==20||i==25||i==35||i==40||i==50||i==55)
            //设置中刻度
            {
                mPaint.setStrokeWidth(4);
                mPaint.setColor(mMiddlecaleColor);
                canvas.drawLine(mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2,
                        mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2+40,mPaint);
                String scaleTv=String.valueOf(i/5);
                canvas.drawText(scaleTv,mWidth/2-mTextPaint.measureText(scaleTv)/2,
                        mHeght/2-mWidth/2+mCircleWidth/2+75,mTextPaint);

            }else
            //设置小刻度
            {
                mPaint.setColor(mSmallScaleColor);
                mPaint.setStrokeWidth(2);
                canvas.drawLine(mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2,
                        mWidth/2,mHeght/2-mWidth/2+mCircleWidth+30,mPaint);
            }
            canvas.rotate(6,mWidth/2,mHeght/2);
        }
    }

这个方法代码看起来也没有什么主要是把一个圆分成60份,因为我们钟表上是有60个刻度,其中设置了4个大刻度分别为0,15,30,45.分别对应的钟表中12点  3点  6点和9点,如果这个地方你有什么疑惑的吧你可以看看你的手表或者钟表就明白了,同时里面也设置了8个中等刻度分别为5,10,20,25,35,40,50,55为中刻度,其实对应的就是1,2,4,5,7,8,10,11点。这里主要是自己觉得这样分明好看而已,如果没有强迫症的你可以直接设置都是大刻度就可以了。其他的都为小刻度,根据自己在attr设置的颜色和尺寸分别设置画笔paint来绘制就可以了。看下我们的效果变成了这样子:

技术分享

第三个方法就是绘制指针:

 /**
     * 绘制指针
     * @param canvas
     */
    private void drawPointer(Canvas canvas) {
        Calendar mCalendar=Calendar.getInstance();
        //获取当前小时数
        int hours = mCalendar.get(Calendar.HOUR);
        //获取当前分钟数
        int minutes = mCalendar.get(Calendar.MINUTE);
        //获取当前秒数
        int seconds=mCalendar.get(Calendar.SECOND);

        mPaint.setStrokeCap(Paint.Cap.ROUND);
        //绘制时针
        canvas.save();
        mPaint.setColor(mHourHandColor);
        mPaint.setStrokeWidth(mHourHandWidth);
        //这里计算时针需要旋转的角度 实现原理是计算出一共多少分钟除以60计算出真实的小时数(带有小数,为了更加准确计算度数),已知12小时是360度,现在求出了实际小时数比例求出角度
        Float hoursAngle = (hours * 60 + minutes) / 60f / 12f  * 360;
        canvas.rotate(hoursAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth/2f*0.5f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();


        //绘制分针
        canvas.save();
        mPaint.setColor(mMinuteHandColor);
        mPaint.setStrokeWidth(mMinuteHandWidth);
        //这里计算分针需要旋转的角度  60分钟360度,求出实际分钟数所占的度数
        Float minutesAngle = (minutes*60+seconds) / 60f/ 60f * 360;
        canvas.rotate(minutesAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.7f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();

        //绘制中间的圆圈
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(mWidth/2,mHeght/2,20,mPaint);
        canvas.restore();


        //绘制秒针
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        //这里计算秒针需要旋转的角度  60秒360度,求出实际秒数所占的度数
        Float secondAngle = seconds/60f*360;
        canvas.rotate(secondAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.8f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.2f, mPaint);
        canvas.restore();


    }

其实这个方法我注释已经写的很详细了,首先我们需要获取到当前的时间,这个大家都是经常写的没啥问题。主要就是如何设定时分秒指针的位置才是关键。这里我使用一个很巧妙的方法,让绘制变得简单了些。

先看下绘制时针:

  //绘制时针
        canvas.save();
        mPaint.setColor(mHourHandColor);
        mPaint.setStrokeWidth(mHourHandWidth);
        //这里计算时针需要旋转的角度 实现原理是计算出一共多少分钟除以60计算出真实的小时数(带有小数,为了更加准确计算度数),已知12小时是360度,现在求出了实际小时数比例求出角度
        Float hoursAngle = (hours * 60 + minutes) / 60f / 12f  * 360;
        canvas.rotate(hoursAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth/2f*0.5f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();

前面三行代码就不详细说了,Canvas.save方法作用就是将之前所有已经绘制的图片保存起来。为后续操作在新的图层上操作。和photoshop有点一个意思。大家都知道当我们获取到当前小时数了以后我们就应该直接把时针指到对应的时数上吗?肯定不是吧,比如是3点半时针是指到3与4之间的位置对吧。所以我们这里需要获取到当前的分钟数再加上小时数才是真实的当前小时数(这里其实秒针也需要计算的,但是这里忽略不计了,如果你比我还强迫症的话可以加上)。当我们知道当前实际的小时数的时候,就很简单了,因为我们知道一圈360度平均分了12小时,这个别告诉我不知道啊,要是这个常识都不知道,你去面壁吧,所以只要360/12*真实的小时数就是需要旋转的角度。这么想是不是很简单了。计算出角度以后先吧canvas.rotate旋转这个角度在绘制一个直线就ok了,哈哈哈,是不是so esey,其他分针和秒针一样的道理,这里就不多说了,看代码直接能看懂的。


        //绘制分针
        canvas.save();
        mPaint.setColor(mMinuteHandColor);
        mPaint.setStrokeWidth(mMinuteHandWidth);
        //这里计算分针需要旋转的角度  60分钟360度,求出实际分钟数所占的度数
        Float minutesAngle = (minutes*60+seconds) / 60f/ 60f * 360;
        canvas.rotate(minutesAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.7f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();

        //绘制中间的圆圈
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(mWidth/2,mHeght/2,20,mPaint);
        canvas.restore();


        //绘制秒针
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        //这里计算秒针需要旋转的角度  60秒360度,求出实际秒数所占的度数
        Float secondAngle = seconds/60f*360;
        canvas.rotate(secondAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.8f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.2f, mPaint);
        canvas.restore();


其中有个绘制中间的圆圈是我的强迫症所致,觉得中间加个圆圈好看点。执行这个方法后我们的效果就成这样了:


技术分享


哈哈下面就剩下最后一步了,就是让指针动起来,没错就是动起来,其实大家也在意了我们绘制指针的时候是在方法里直接获取了当前时间设置指针的位置的,所以说只要我们搞个定时器一秒刷新下ui就大功告成了,这里就搞个hander发个空消息。

    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case START_CLOCK:
                    //更新时分秒
                    invalidate();
                    //每隔1秒更新一次
                    handler.sendEmptyMessageDelayed(START_CLOCK,1000);
                    break;
            }

        }
    };

就成了下面的效果了:

技术分享


是不是很简单呢?附上github:https://github.com/dalong982242260/CustomViewStudy


Android自定义一个属于自己的时间钟表