美文网首页
唔得闲都要搞事之实现荧光时钟

唔得闲都要搞事之实现荧光时钟

作者: Arestory | 来源:发表于2016-12-17 18:11 被阅读83次

前段时间看过郭霖大神公众号推送的一篇文章《一步步实现精美的钟表界面》,忍不住自己也去实现一番,然后去搜了一些钟表图片,发现有个挺不错的,然后就按照文章中的一些思路开始一步步实现这个荧光时钟。

效果图

Screenshot_20170818-094615.png

1.第一步,创建自定义View继承View,实现构造方法,如下

  public LightClock(Context context) {
        super(context);
    }

    public LightClock(Context context, AttributeSet attrs) {
        super(context, attrs);
        obtainStyledAttrs(attrs);

    }

2.第二步,添加必要属性,创建自定义资源文件

     /**
     * 最外层颜色
     */
    private int outCircleColor;
    /**
     * 第二层颜色
     */
    private int secondCircleColor;
    /**
     * 表头颜色
     */
    private int clockColor;
    /**
     * 一刻钟的刻度的颜色
     */
    private int quarterColor;
    /**
     * 其他刻度的颜色
     */
    private int minuteColor;
    /**
     * 圆心小圆的颜色
     */
    private int centerCircleColor;
    /**
     * 指针的颜色
     */
    private int wiseColor;
    /**
     * 指针上正方形的颜色
     */
    private int squareColorOfWise;
    /**
     * 指针上圆形颜色
     */
    private int circleColorOfWise;
    /**
     * 指针上 三角形的颜色
     */
    private int triangleColorOfWise;

    /**
     * 时钟半径
     */
    private float radiusOfClock;
    /**
     *  中间小圆的半径
     */
    private float radiusOfCenterCircle;
    /**
     * 时钟的中心点
     */
    private float centerXofClock, centerYofClock;
    /**
     * 一刻钟的圆的半径
     */
    private float radiusOfquarter;
    /**
     * 其他刻度的圆的半径
     */
    private float radiusOfminute;

    /**
     * 正方形的宽度
     */
    private float squareWidth;
    /**
     * 三角形高度,该三角形的高=2*底边
     */
    private float triangleHeight;
    /**
     * 指针圆形半径
     */
    private float radiusOfWiseCircle;

res目录下创建自定 attr.xml

    <declare-styleable name="LightClock">
        <attr name="outsideCircleColor" format="color"></attr>
        <attr name="secondCircleColor" format="color"></attr>
        <attr name="clockColor" format="color"></attr>
        <attr name="quarterColor" format="color"></attr>
        <attr name="minuteColor" format="color"></attr>
        <attr name="centerCircleColor" format="color"></attr>
        <attr name="wiseColor" format="color"></attr>
        <attr name="squareColorOfWise" format="color"></attr>
        <attr name="circleColorOfWise" format="color"></attr>
        <attr name="triangleColorOfWise" format="color"></attr>
    </declare-styleable>

构造方法中获取属性并且设置默认值,添加异常情况的处理(一旦出现异常,使用全部默认值)

 public LightClock(Context context, AttributeSet attrs) {
        super(context, attrs);
        obtainStyledAttrs(attrs);

    }


    /**
     * 获取样式,出现异常时取默认值
     *
     * @param attrs
     */
    private void obtainStyledAttrs(AttributeSet attrs) {
        TypedArray array;

        try {
            array = getContext().obtainStyledAttributes(attrs, R.styleable.LightClock);
            outCircleColor = getColor(array, R.styleable.LightClock_outsideCircleColor, Color.rgb(187, 184, 183));//#bbb8b7
            secondCircleColor = getColor(array, R.styleable.LightClock_secondCircleColor, Color.rgb(105, 104, 103));//#696867
            clockColor = getColor(array, R.styleable.LightClock_clockColor, Color.rgb(0, 0, 0));//#000000
            quarterColor = getColor(array, R.styleable.LightClock_quarterColor, Color.rgb(77, 255, 255));//4dffff
            minuteColor = getColor(array, R.styleable.LightClock_minuteColor, Color.rgb(105, 104, 103));//#696867
            centerCircleColor = getColor(array, R.styleable.LightClock_centerCircleColor, Color.rgb(255, 158, 11));//#ff9e0b
            wiseColor = getColor(array, R.styleable.LightClock_wiseColor, Color.rgb(105, 104, 103));//#696867
            squareColorOfWise = getColor(array, R.styleable.LightClock_squareColorOfWise, Color.rgb(0, 255, 0));//#00ff00
            circleColorOfWise = getColor(array, R.styleable.LightClock_circleColorOfWise, Color.rgb(255, 54, 54));//#ff3636
            triangleColorOfWise = getColor(array, R.styleable.LightClock_triangleColorOfWise, Color.rgb(255, 255, 49));//#ffff31


        } catch (Exception e) {

            outCircleColor=Color.rgb(187, 184, 183);
            secondCircleColor = Color.rgb(105, 104, 103);//#696867
            clockColor = Color.rgb(0, 0, 0);//#000000
            quarterColor = Color.rgb(77, 255, 255);//4dffff
            minuteColor = Color.rgb(105, 104, 103);//#696867
            centerCircleColor =Color.rgb(255, 158, 11);//#ff9e0b
            wiseColor = Color.rgb(105, 104, 103);//#696867
            squareColorOfWise = Color.rgb(0, 255, 0);//#00ff00
            circleColorOfWise =Color.rgb(255, 54, 54);//#ff3636
            triangleColorOfWise = Color.rgb(255, 255, 49);//#ffff31


        }
    }
    /**
     * 获取相应颜色
     *
     * @param array
     * @param colorId
     * @param defaultColor
     * @return
     */
    public int getColor(TypedArray array, int colorId, int defaultColor) {


        return array.getColor(colorId, defaultColor);

    }

测量 view 长宽,确保 view长宽相等,需重写onMeasure方法,使得表盘始终只占用一个正方形的空间

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED || heightMeasureSpec == MeasureSpec.AT_MOST || heightMeasureSpec == MeasureSpec.UNSPECIFIED) {
            try {
                throw new NoDetermineSizeException("宽度高度至少有一个确定的值,不能同时为wrap_content");
            } catch (NoDetermineSizeException e) {
                e.printStackTrace();
            }
        } else { 
            // 获得该view真实的宽度和高度
            int mRealWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
            int mRealHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

            Log.v("","mRealHeight="+mRealHeight);
            //取最小值
            int width=Math.min(mRealHeight,mRealWidth);
            //设置长宽相等
            setMeasuredDimension(width,width);

        }


    }

由于在onMeasure方法中设置了view 的长宽相等,需重写 onSizeChanged方法

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //设置时钟的半径为 view 边长的一半
        radiusOfClock = (Math.min(w, h)) / 2; 
        //设置中心点
        centerYofClock = radiusOfClock;
        centerXofClock = radiusOfClock;
        //整点刻度的圆半径为时钟半径的1/40
        radiusOfquarter=0.025f*radiusOfClock;
        //非整点刻度的圆半径为时钟半径的1/100
        radiusOfminute=0.01f*radiusOfClock;
        //时钟中心小圆的半径为时钟半径的3/100
        radiusOfCenterCircle=0.03f*radiusOfClock;
        //分针上的小圆半径为整点刻度小圆的1.6倍
        radiusOfWiseCircle=1.6f*radiusOfquarter;
        //时针上的正方形边长为时钟半径的1/20
        squareWidth=0.05f*radiusOfClock;
        //三角形高度=正方形边长
        triangleHeight=squareWidth;

    }

3.第三步,开始绘制时钟

所有绘制动作均在 ondraw 方法中执行,为了减少坐标计算量,利用 canvas的translate方法将画布坐标原点从(0,0)转移到时钟中心的坐标点

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //先锁定画布
        canvas.save();
        //将画布原点坐标从0,0转移到时钟中心的坐标点
        canvas.translate(centerXofClock, centerYofClock);
        drawTheOutCircle(canvas);
        drawSecondCircle(canvas);
        drawBoardCicle(canvas);
        //绘制刻度
        drawScale(canvas);
        //绘制时分秒针
        drawClockwise(canvas);
        //绘制中心小圆
        drawCenterCircle(canvas);
        //还原画布位置
        canvas.restore();
        //每隔一秒刷新一次
        postInvalidateDelayed(1000);


    }

3.1 绘制外圈圆

 /**
     * 画最外圈
     * @param canvas
     */
    private void drawTheOutCircle(Canvas canvas) {

        Paint paint = getPaint(outCircleColor);

        canvas.drawCircle(0, 0, radiusOfClock, paint);
    }

    /**
     * 画第二个圈
     * @param canvas
     */
    private void drawSecondCircle(Canvas canvas){
        Paint paint = getPaint(secondCircleColor);
        canvas.drawCircle(0, 0, radiusOfClock*0.95f, paint);

    }

    /**
     * 画中间时钟的圈
     * @param canvas
     */
    private void drawBoardCicle(Canvas canvas){
        Paint paint = getPaint(clockColor);
        canvas.drawCircle(0, 0, radiusOfClock*0.92f, paint);
    }

效果

Screenshot_20170818-094805.png

3.2绘制刻度

绘制的时候我们希望能够直接在X轴或者Y轴上绘制线条,但当前的x,y相对于原点来说都是水平,垂直的,那么我们就可以想到将坐标系每次旋转6°进行绘制,一共旋转60次,并且每次都是在x轴或者y轴上绘制.这样的话就能减少大量的坐标换算量(PS:当时笔者居然想着要用到高中知识利用角度去换算 x和y值,简直奔溃[捂脸])

 /**
     * 画刻度
     * @param canvas
     */
    private void drawScale(Canvas canvas){ 
        //共有60个刻度,循环画60次,每个刻度占6度
        for(int i=0;i<60;i++){
            //整点
            if(i%5==0){
                Paint scalePaint =getPaint(quarterColor);
 canvas.drawCircle(0,-0.8f*radiusOfClock,radiusOfquarter,scalePaint);

            }else{
                Paint scalePaint =getPaint(minuteColor);
                canvas.drawCircle(0,-0.8f*radiusOfClock,radiusOfminute,scalePaint);


            } 
            //每画一个刻度,将当前画布旋转6度
            canvas.rotate(6);
        } 
    }

效果

Screenshot_20170818-094853.png

3.3 绘制时分秒针


    /**时针占半径的比例*/
    public static final float HOUR_WISE_PERCENT=0.5F;
    /**分针占半径的比例*/
    public static final float MINUTE_WISE_PERCENT=0.65F;
    /**秒针占半径的比例*/
    public static final float SECOND_WISE_PERCENT=0.8F;
    /**
     * 用于绘制时分秒针
     * 绘制每一根针前都需要调用 canvas.save()去锁定当前画布,再根据时间旋转角度,画完需要调用 canvas.restore()还原画布
     * @param canvas
     */
    private void drawClockwise(Canvas canvas) {

        Paint squarePaint = getPaint(squareColorOfWise);
        Paint circlePaint = getPaint(circleColorOfWise);
        Paint trianglePaint=getPaint(triangleColorOfWise);
        Paint wisePaint = getPaint(wiseColor);
        //指针的宽度
        wisePaint.setStrokeWidth(4f);
        
        //获取当前时间的时分秒
        Calendar calendar = Calendar.getInstance(); 
        //计算各条针应该旋转的角度
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
         int minute = calendar.get(Calendar.MINUTE);
         int second = calendar.get(Calendar.SECOND);
         if (timeChangeListener != null) {
            timeChangeListener.onTimeChange(hour, minute, second);
        } 
        int angleHour = (hour % 12) * 360 / 12;//时针转过的角度
        int angleMinute = minute * 360 / 60;//分针转过的角度
        int angleSecond = second * 360 / 60;//秒针转过的角度
        
        //绘制时针
        canvas.save();
        canvas.rotate(angleHour);
        canvas.drawLine(0, -HOUR_WISE_PERCENT*radiusOfClock, 0, 0, wisePaint);
        //绘制时针尾的正方形
        canvas.drawRect(-squareWidth,-HOUR_WISE_PERCENT*radiusOfClock-squareWidth,squareWidth,squareWidth-HOUR_WISE_PERCENT*radiusOfClock,squarePaint);
        canvas.restore();
        
        //绘制分针
        canvas.save();
        canvas.rotate(angleMinute);
        canvas.drawLine(0, -MINUTE_WISE_PERCENT*radiusOfClock, 0, 0, wisePaint);
        //绘制分针尾的小圆
        canvas.drawCircle(0, -MINUTE_WISE_PERCENT*radiusOfClock,radiusOfWiseCircle,circlePaint);
         canvas.restore();

        //绘制秒针
        canvas.save();
        canvas.rotate(angleSecond);
        canvas.drawLine(0, - SECOND_WISE_PERCENT*radiusOfClock, 0, 0, wisePaint);
        //设置三角形的路径
        Path trianglePath= new Path();
        //设置起点
        trianglePath.moveTo(0,- SECOND_WISE_PERCENT*radiusOfClock-triangleHeight);
        //从起点划线移到第二顶点
        trianglePath.lineTo(-triangleHeight,- SECOND_WISE_PERCENT*radiusOfClock+triangleHeight);
        //从第二个顶点移到第三个顶点
        trianglePath.lineTo(triangleHeight,- SECOND_WISE_PERCENT*radiusOfClock+triangleHeight);
        //绘制三角形
        canvas.drawPath(trianglePath,trianglePaint);
        canvas.restore(); 
    }
 

效果

Screenshot_20170818-094615.png

完整代码请浏览以下链接
https://github.com/yuwenque/ClockSample

相关文章

网友评论

      本文标题:唔得闲都要搞事之实现荧光时钟

      本文链接:https://www.haomeiwen.com/subject/efxamttx.html