目录

 

前言

效果图

项目实现

用到的属性

重写三个构造方法

初始化各个指针的画笔,指针的矩形

重写 onMeasure方法

获取系统的时间

重写ondraw方法

最后给时钟添加上属性动画


前言

之前项目有个需求是在桌面上增加一个桌面时钟,当时是参考别人的博客,在其基础上添加了属性动画然后实现的。因此今天特地重新写了一遍这个自定义view 加深印象并分享出来

效果图

用手机拍的视频,然后转为GIF的,因为是试用的,所以就是这个渣渣效果,还有水印,求推荐一个好用的格式转换工具

android view使用动画 android 自定义view动画_移动开发

项目实现

为了编写方便,这里没有用到自定义属性,有需要的话,可以自行添加自定义属性,同时在代码中注释很详细,文章这里只简单介绍一下

用到的属性

private Paint hPaint ,mPaint ,sPaint ,circlePaint ,cPointPaint,paintDegree,txtPaint,titlePaint;
    private final int maxWidth = 400;
    int width ,height;

    int hSound = 0;
    int mSound = 0;
    int sSound = 0;

    private int hcount = 0;//当前小时数
    private int mcount = 0;//当前分钟数
    private int scount = 0;//当前秒钟数

    private final int hScale = 30;//每小时之间30度
    private static final int mScale = 6;//每分钟之间是6度

    RectF hRect , mRect ,sRect ;//三个
    boolean first = true;//执行动画之后再转动指针

重写三个构造方法

public ClockView(Context context) {
        super(context);
        init();
    }

    public ClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

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

初始化各个指针的画笔,指针的矩形

这里各种new操作不要放在Ondraw方法中,避免重复实现

private void init(){
        //外圆盘画笔
        circlePaint = new Paint();
        circlePaint.setAntiAlias(true);
        circlePaint.setDither(true);
        circlePaint.setStyle(Paint.Style.STROKE);
        circlePaint.setColor(Color.BLACK);
        circlePaint.setStrokeWidth(1);

        //圆心画笔
        cPointPaint = new Paint();
        cPointPaint.setAntiAlias(true);
        cPointPaint.setDither(true);
        cPointPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        cPointPaint.setColor(0xff4CAF50);//绿色
        cPointPaint.setStrokeWidth(2);

        //时针画笔
        hPaint = new Paint();
        hPaint.setAntiAlias(true);
        hPaint.setDither(true);
        hPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        hPaint.setColor(0xff349CE2);//蓝色
        hPaint.setStrokeWidth(7);
        //时针矩形
        hRect = new RectF(-16,0,89,0);

        //分针画笔
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setColor(0xffDB2540);//红色
        mPaint.setStrokeWidth(6);
        //分针矩形
        mRect = new RectF(-25,0,140,0);

        //秒针画笔
        sPaint = new Paint();
        sPaint.setAntiAlias(true);
        sPaint.setDither(true);
        sPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        sPaint.setColor(0xff4CAF50);//绿色
        sPaint.setStrokeWidth(5);
        //秒钟矩形
        sRect = new RectF(-30,0,180,0);

        //刻度画笔
        paintDegree  = new Paint();
        paintDegree.setColor(0xff000000);
        paintDegree.setStyle(Paint.Style.STROKE);
        paintDegree.setAntiAlias(true);

        //文字画笔
        txtPaint = new Paint();
        txtPaint.setColor(0xff000000);
        txtPaint.setTextSize(20);
        txtPaint.setAntiAlias(true);

        //标题画笔
        titlePaint = new Paint();
        titlePaint.setColor(0xff000000);
        titlePaint.setTextSize(45);
        titlePaint.setAntiAlias(true);

    }

重写 onMeasure方法

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        if (widthMode == MeasureSpec.AT_MOST && heightMode ==MeasureSpec.AT_MOST){
            setMeasuredDimension(maxWidth,maxWidth);
        }else if (widthMode == MeasureSpec.AT_MOST ){
            setMeasuredDimension(maxWidth,width);
        }else if (heightMode ==MeasureSpec.AT_MOST ){
            setMeasuredDimension(height,maxWidth);
        }
    }

获取系统的时间

private void getDatas() {
        SimpleDateFormat format = new SimpleDateFormat("HH,mm,ss");
        String time = format.format(new Date());
        try {
            String s[] = time.split(",");
            hcount = Integer.parseInt(s[0]);
            mcount = Integer.parseInt(s[1]);
            scount = Integer.parseInt(s[2]);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

重写ondraw方法

首先获取并计算宽高,设置各指针长度以及外圆的半径 

super.onDraw(canvas);
        width = getMeasuredWidth()-getPaddingStart()-getPaddingEnd();
        height = getMeasuredHeight()-getPaddingBottom()-getPaddingTop();
        width = height = Math.min(width,height);
         //圆心点
        int cpoints = width/2;
        hSound = width/8;//时针长度
        mSound = width/6;//分针长度
        sSound = width/4;//秒针长度

        int radius =cpoints*3/4;//外圆半径

借用一下别人的图

android view使用动画 android 自定义view动画_android studio_02

 

这里判断是否是第一次显示时钟,等于true为第一次,然后绘制标题,位置的计算根据上图

if(!first){
            getDatas();
        }

        String titleText = "三原色时钟";
        //X坐标等于圆心的X坐标减去文字的一半的长度,Y坐标等于圆心的Y坐标减圆的半径再减20
        canvas.drawText(titleText,cpoints - titlePaint.measureText(titleText) / 2 ,cpoints-radius-20,titlePaint);

 

然后绘制刻度,圆盘

//画出12个小时的刻度线及文字
        for (int i = 0; i < 12; i++) {
            String txtTime = Integer.toString(i);
            //3,6,9,12比其他的略粗略长
            if(i%3==0) {
                if (i==0){
                    txtTime = "12";
                }
                paintDegree.setStrokeWidth(5);
                canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 20, paintDegree);
            }else{
                paintDegree.setStrokeWidth(4);
                canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 15, paintDegree);
            }
            canvas.drawText(txtTime,cpoints - txtPaint.measureText(txtTime) / 2,height / 2 - radius + 40,txtPaint);
            canvas.rotate(hScale, width / 2, height / 2);
        }

        //画出60个分钟的刻度线
        for (int x = 0; x < 60; x++) {
            paintDegree.setStrokeWidth(3);
            if (x % 5 != 0) {//当x % 5 == 0时即是时钟刻度,因此不需要绘制,避免重复绘制
                canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 8, paintDegree);
            }
            canvas.rotate(mScale, width / 2, height / 2);
        }

        //画外层圆
        canvas.drawCircle(cpoints,cpoints,radius,circlePaint);
        //画内层圆
        canvas.drawCircle(cpoints,cpoints,radius/6,circlePaint);
        //平移至中心点
        canvas.translate(cpoints,cpoints);
        //保存画布
        canvas.save();

最后是绘制三个指针

//int hRotate = 270 + hScale * hcount;
        int offset = 30 * mcount / 60;
        offset -= offset % mScale;//时针相对分针数,有一个偏移量
        int hRotate = 270 + hScale * hcount + offset;
        canvas.rotate(hRotate);
//        canvas.drawLine(0, -10, 0, hSound, hPaint);//画时针

        canvas.drawRoundRect(hRect,15,15,hPaint);//画时针

        canvas.restore();
        canvas.save();
        int mRotate = 270 + mScale * mcount ;
        canvas.rotate(mRotate);

        canvas.drawRoundRect(mRect,25,25,mPaint);//画分针
        //canvas.drawLine(0, -15, 0, mSound, mPaint);//画分针

        canvas.restore();
        canvas.save();
        //一圈360度,总共60秒,因此时间每多一秒,度数加6
        int sRotate = 270 + mScale * scount ;
        canvas.rotate(sRotate);
        //canvas.drawLine(0, -25, 0, sSound, sPaint);//画秒针

        canvas.drawRoundRect(sRect,15,15,sPaint);//画秒针

 

绘制指针上的圆心点

canvas.drawCircle(0,0,6,cPointPaint);//画圆心

 

第一次出现时不转动指针

if(!first){
            postInvalidateDelayed(1000);
        }

 

最后给时钟添加上属性动画

在View中添加一个公共方法给外部调用

public void startAnim(){
        getDatas();
        final ValueAnimator animatorh = ValueAnimator.ofInt(0,hcount>12?hcount-12:hcount);//大于十二点时减去12 避免转两圈
        final ValueAnimator animatorm=ValueAnimator.ofInt(0,mcount);
        final ValueAnimator animators=ValueAnimator.ofInt(0,scount);

        //设置动画时长
        animatorh.setDuration(1500);
        animatorm.setDuration(1500);
        animators.setDuration(1500);
        animatorh.setInterpolator(new LinearInterpolator());
        animatorh.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                hcount = (int)animation.getAnimatedValue();
            }
        });
        animatorh.start();

        animatorm.setInterpolator(new LinearInterpolator());
        animatorm.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mcount = (int)animation.getAnimatedValue();
            }
        });
        animatorm.start();

        animators.setInterpolator(new LinearInterpolator());
        animators.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                scount = (int)animation.getAnimatedValue();
                postInvalidate();//添加之后动画才会执行,不然看不到效果
            }
        });
        animators.start();
        //添加动画完成时的监听,在动画完成之后开始指针的转动
        animators.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                first = false ;
                postInvalidate();
            }
        });
    }

 

有时不需要执行动画的话,可以再添加一个公共方法直接设置first 为false

public void SetFirstInit(Boolean  ttt) {
        first =ttt;
    }

 

//然后在布局添加自定义view即可

android view使用动画 android 自定义view动画_移动开发_03

 

在Activity中使用ClockView并根据需求调用对应的方法

android view使用动画 android 自定义view动画_canvas_04

 

到这里文章就结束了,觉得还算可以的,欢迎点赞啊