欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一个简单的时钟View(关键点:Canvas的平移与旋转)

程序员文章站 2024-01-31 12:50:25
...

通过Canvas的平移与旋转简化绘图逻辑是一个非常有用的技巧,下面的时钟view就是利用这个方法完成的,省去了使用三角函数计算坐标的麻烦。
一个简单的时钟View(关键点:Canvas的平移与旋转)

package com.example.swt369.simpleclock;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Toast;

import java.util.Calendar;

/**
 * Created by swt369 on 2017/8/20.
 */

public class ClockView extends View {
    //时钟各个部件的长度
    private float mScaleLengthLong;
    private float mScaleLengthShort;
    private float mTickLengthHour;
    private float mTickLengthMinute;
    private float mTickLengthSecond;
    //view的宽和高
    private float mWidth;
    private float mHeight;
    //时钟半径
    private float mRadius;

    //绘制各部件时用的Paint
    private Paint mPaintScaleLong;
    private Paint mPaintScaleShort;
    private Paint mPaintOutline;
    private Paint mPaintNum;
    private Paint mPaintTickHour;
    private Paint mPaintTickMinute;
    private Paint mPaintTickSecond;

    private ViewTreeObserver.OnPreDrawListener onPreDrawListener;
    public ClockView(final Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        onPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
            //获取view宽高并计算各个部件的长度
            @Override
            public boolean onPreDraw() {
                mWidth = getMeasuredWidth();
                mHeight = getMeasuredHeight();
                mRadius = Math.min(mWidth,mHeight) / 2 * 0.95f;
                mScaleLengthLong = mRadius * 0.1f;
                mScaleLengthShort = mRadius * 0.05f;
                mTickLengthHour = mRadius * 0.3f;
                mTickLengthMinute = mRadius * 0.45f;
                mTickLengthSecond = mRadius * 0.6f;
                return true;
            }
        };
        getViewTreeObserver().addOnPreDrawListener(onPreDrawListener);

        //点击显示具体时间
        setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Calendar calendar = Calendar.getInstance();
                String time = String.format("当前时间:%02d:%02d:%02d",
                        calendar.get(Calendar.HOUR_OF_DAY),calendar.get(Calendar.MINUTE),calendar.get(Calendar.SECOND));
                Toast.makeText(context,time, Toast.LENGTH_SHORT).show();
            }
        });

        //初始化所有Paint对象
        initializePaints();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if(mWidth == 0 || mHeight == 0){
            return;
        }
        if(onPreDrawListener != null){
            getViewTreeObserver().removeOnPreDrawListener(onPreDrawListener);
            onPreDrawListener = null;
        }
        //绘制时钟
        drawClock(canvas);
        //一秒后重绘
        postInvalidateDelayed(1000);
    }

    private void drawClock(Canvas canvas) {
        //保存原始状态
        canvas.save();

        //将坐标系原点移到中心,并逆时针旋转90度。完成后x轴朝上。
        canvas.translate(mWidth / 2,mHeight / 2);
        canvas.rotate(-90);

        //画外围轮廓
        canvas.drawCircle(0,0,mRadius, mPaintOutline);

        //画刻度
        for(int i = 0 ; i < 12 ; i++){
            String num = String.valueOf(i == 0 ? 12 : i);
            if(i % 3 == 0){
                canvas.drawLine(mRadius,0,mRadius - mScaleLengthLong,0, mPaintScaleLong);
            }else {
                canvas.drawLine(mRadius,0,mRadius - mScaleLengthShort,0, mPaintScaleShort);
            }
            canvas.drawText(num,mRadius - mScaleLengthLong - mPaintNum.measureText(num) * 2,0, mPaintNum);

            //顺时针旋转30度
            canvas.rotate(30);
        }

        Calendar calendar = Calendar.getInstance();

        //画时针
        int hour = calendar.get(Calendar.HOUR);
        canvas.save();
        canvas.rotate(hour * 30);
        canvas.drawLine(0,0,mTickLengthHour,0,mPaintTickHour);
        canvas.restore();

        //画分针
        int minute = calendar.get(Calendar.MINUTE);
        canvas.save();
        canvas.rotate(minute * 6);
        canvas.drawLine(0,0,mTickLengthMinute,0,mPaintTickMinute);
        canvas.restore();

        //画秒针
        int second = calendar.get(Calendar.SECOND);
        canvas.save();
        canvas.rotate(second * 6);
        canvas.drawLine(0,0,mTickLengthSecond,0,mPaintTickSecond);
        canvas.restore();

        //恢复原始状态
        canvas.restore();
    }

    private void initializePaints(){
        mPaintScaleLong = new Paint();
        mPaintScaleLong.setAntiAlias(true);
        mPaintScaleLong.setStrokeWidth(5);

        mPaintScaleShort = new Paint();
        mPaintScaleShort.setAntiAlias(true);
        mPaintScaleShort.setStrokeWidth(3);

        mPaintOutline = new Paint();
        mPaintOutline.setStyle(Paint.Style.STROKE);
        mPaintOutline.setAntiAlias(true);
        mPaintOutline.setStrokeWidth(5);

        mPaintNum = new Paint();
        mPaintNum.setTextSize(30);

        mPaintTickHour = new Paint();
        mPaintTickHour.setAntiAlias(true);
        mPaintTickHour.setStrokeWidth(6);

        mPaintTickMinute = new Paint();
        mPaintTickMinute.setAntiAlias(true);
        mPaintTickMinute.setStrokeWidth(4);

        mPaintTickSecond = new Paint();
        mPaintTickSecond.setAntiAlias(true);
        mPaintTickSecond.setStrokeWidth(2);
    }
}

代码注释已经比较详细了,下面把比较重要的部分再说明一下:

//保存原始状态
canvas.save();

//省略中间部分...

//恢复原始状态
canvas.restore();

在绘图开始时调用canvas.save(),可以保存下未经任何平移、旋转操作的原始画布状态。在所有绘图工作完成后调用canvas.restore(),可以恢复到上一次保存的状态(类似进出栈的感觉)。

//将坐标系原点移到中心,并逆时针旋转90度。完成后x轴朝上。
canvas.translate(mWidth / 2,mHeight / 2);
canvas.rotate(-90);

画布的平移与旋转可能比较抽象,可以想象成坐标系的平移与旋转。调用canvas.translate(dx,dy)相当于将坐标原点向x,y方向移动了dx,dy的距离,调用canvas.rotate(degree)相当于坐标系顺时针旋转了degree°。

//画刻度
for(int i = 0 ; i < 12 ; i++){
    String num = String.valueOf(i == 0 ? 12 : i);
    if(i % 3 == 0){
        canvas.drawLine(mRadius,0,mRadius - mScaleLengthLong,0, mPaintScaleLong);
    }else {
        canvas.drawLine(mRadius,0,mRadius - mScaleLengthShort,0, mPaintScaleShort);
    }
    canvas.drawText(num,mRadius - mScaleLengthLong - mPaintNum.measureText(num) * 2,0, mPaintNum);

    //顺时针旋转30度
    canvas.rotate(30);
}

根据i值决定是画大刻度(90°的倍数)还是小刻度。每次画完一条之后将坐标系顺时针旋转30°,这样保证每次画的刻度线的坐标不变。

//一秒后重绘
postInvalidateDelayed(1000);

作用是在1000ms(1s)后再次执行View的draw流程,产生指针运动的效果。

相关标签: android绘图