自定义View-- 绘制一个表盘

看到一加OS 上的表盘不错,所以仿写一个同时复习一下自定义View的内容。

自定义View-- 绘制一个表盘_第1张图片

分析表盘结构

一个圆形白色表盘,12个刻度点,3个指针,一个中心点,一个当前时间指示点。

采用何种绘制方案

很简单,直接覆写onDraw 方法 ,在canvas 上绘制内容即可,都是最基本的canvas API 绘制操作,绘制过程都是基本的API调用。

  1. 画线
 public void drawLine(float startX, float startY, float stopX, float stopY,
            @NonNull Paint paint) 
  1. 画点
 public void drawPoint(float x, float y, @NonNull Paint paint)
  1. 画圆
public void drawCircle(float cx, float cy, float radius, @NonNull Paint paint)
  1. 操作画布
    平移画布
public void translate(float dx, float dy)

旋转画布

public void rotate(float degrees)

结合平移和旋转操作让简化坐标计算。

让钟表动起来

基本Handler操作,每隔一秒执行一次重绘操作。
注意点:自动开启/停止绘制操作的时机选在View的 onAttachedToWindow/onDetachedFromWindow回调中,简化使用。

完整代码

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import java.util.Calendar;
/**
 * @author Bridgeliang
 * @date 2019/5/19
 */
public class ClockView extends View {

    private static final String TAG = "ClockView";

    private Paint mPaint;
    private int width, height, radius;
    private int lenDot, lenHour, lenMin, lenSec;

    private int minSqureSize;


    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);
        init();
    }

    private void init() {
        mPaint = new Paint();
        minSqureSize = dip2px(getContext(), 200);
    }

    private void drawBasicView(Canvas canvas) {
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setAntiAlias(true);
        //绘制表盘
        mPaint.setColor(Color.WHITE);
        canvas.translate(width / 2, height / 2);
        canvas.rotate(-90);
        canvas.drawCircle(0, 0, radius, mPaint);
        //绘制12个刻度点
        mPaint.setColor(Color.GRAY);
        mPaint.setStrokeWidth(dip2px(getContext(), 5));
        canvas.save();
        for (int i = 0; i < 12; i++) {
            canvas.drawPoint(0, -lenDot, mPaint);
            canvas.rotate(30);
        }
        canvas.restore();
        //绘制逻辑
        int[] time = getCurrentTime();
        int hour = time[0];
        int min = time[1];
        int sec = time[2];
        float hourRotateDegree = (hour * 3600f + min * 60f + sec) / (12f * 60f * 60f) * 360f;
        float minRotateDegree = min * 1f / 60f * 360f;
        float secRotateDegree = sec * 1f / 60f * 360f;
        //时针
        mPaint.setColor(Color.BLACK);
        canvas.save();
        canvas.rotate(hourRotateDegree);
        canvas.drawLine(0, 0, lenHour, 0, mPaint);
        canvas.restore();
        //分针
        canvas.save();
        canvas.rotate(minRotateDegree);
        canvas.drawLine(0, 0, lenMin, 0, mPaint);
        canvas.restore();
        //秒针
        canvas.save();
        mPaint.setStrokeWidth(dip2px(getContext(), 1));
        mPaint.setColor(Color.RED);
        canvas.rotate(secRotateDegree);
        canvas.drawLine(0, 0, lenSec, 0, mPaint);
        canvas.restore();
        //绘制中心点
        mPaint.setColor(Color.GRAY);
        canvas.drawCircle(0, 0, dip2px(getContext(), 4), mPaint);
        //绘制边缘点
        mPaint.setColor(Color.GREEN);
        canvas.save();
        canvas.rotate(hourRotateDegree);
        canvas.drawCircle(radius, 0, dip2px(getContext(), 8), mPaint);
        canvas.restore();
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            invalidate();
            sendMessageDelayed(obtainMessage(), 1000);
        }
    };

    private int[] getCurrentTime() {
        int[] time = new int[3];
        time[0] = Calendar.getInstance().get(Calendar.HOUR);
        time[1] = Calendar.getInstance().get(Calendar.MINUTE);
        time[2] = Calendar.getInstance().get(Calendar.SECOND);
        Log.e(TAG, String.format("%d:%d:%d", time[0], time[1], time[2]));
        return time;
    }


    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Log.e(TAG, "onAttachedToWindow: ");
        startTimer();
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        Log.e(TAG, "onDetachedFromWindow: ");
        stopTimer();
    }

    private void startTimer() {
        mHandler.removeCallbacksAndMessages(null);
        mHandler.obtainMessage().sendToTarget();
    }

    private void stopTimer() {
        mHandler.removeCallbacksAndMessages(null);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        width = w;
        height = h;
        radius = Math.min(width, height) / 2 - dip2px(getContext(), 8);
        lenDot = radius * 7 / 8;
        lenMin = radius * 5 / 8;
        lenHour = radius * 3 / 8;
        lenSec = radius * 6 / 8;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        Log.e(TAG, "onMeasure: ");
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //支持wrap_content
        if (widthMode == MeasureSpec.AT_MOST)
            widthSize = minSqureSize;
        if (heightMode == MeasureSpec.AT_MOST)
            heightSize = minSqureSize;
        setMeasuredDimension(widthSize, heightSize);
    }
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        Log.e(TAG, "onLayout: ");
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.e(TAG, "onDraw: ");
        drawBasicView(canvas);
    }

    private int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }
}

效果图

自定义View-- 绘制一个表盘_第2张图片

注意点

  1. 避免大对象分配 注意paint 复用。
  2. 注意及时移除不再使用的handler
  3. 支持wrap_content 模式

你可能感兴趣的:(Android)