自定义View_自定义时钟ClockView

前段时间一直忙着学理财给自己充电,有一段时间没写代码了,前两天做了一个梦,梦见自己在写代码,是做了一个自定义时钟的一个东西,然后我醒来后就想着把这个效果实现以下,也算是“梦”想成真了。先来看下效果图吧~

OK,话不多说,直接安排~

1.初始化画笔

定义好需要的画笔,这里我们先定义4个,分别是绘制背景、基本元素、指针、数字四个Paint画笔。然后初始化进行画笔的基本设置。

/**
 * 背景绘制画笔
 */
private Paint mPaintBg;
/**
 * 时钟元素画笔
 */
private Paint mPaint;
/**
 * 绘制指针的画笔
 */
private Paint mPaintLine;
/**
 * 绘制数字的画笔
 */
private Paint mPaintText;

//------------------------------------------------------------------------------

/**
 * 初始化
 */
private void initView() {
    mPaintBg = new Paint();
    mPaintBg.setAntiAlias(true);
    mPaintBg.setColor(Color.BLACK);
    mPaintBg.setStrokeWidth(dip2px(mContext, 2));
    mPaintBg.setStyle(Paint.Style.STROKE);

    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setColor(Color.RED);
    mPaint.setStyle(Paint.Style.FILL);

    mPaintLine = new Paint();
    mPaintLine.setAntiAlias(true);
    mPaintLine.setStyle(Paint.Style.FILL);
    mPaintLine.setStrokeWidth(dip2px(mContext, 2));

    mPaintText = new Paint();
    mPaintText.setAntiAlias(true);
    mPaintText.setColor(Color.BLACK);
    mPaintText.setStyle(Paint.Style.STROKE);
    mPaintText.setTextSize(30);
}

2.初始化变量

这里要定义三个变量来控制三个指针的长度,也用于后面计算坐标。

/**
 * 指针长度-时针
 */
private float mDistanceHour;
/**
 * 指针长度-分针
 */
private float mDistanceMinute;
/**
 * 指针长度-秒针
 */
private float mDistanceSecond;

//------------------------------------------------------------------------------

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    mDistanceHour = w / 5 - dip2px(mContext, 7);
    mDistanceMinute = mDistanceHour + dip2px(mContext, 18);
    mDistanceSecond = mDistanceHour + dip2px(mContext, 45);
}

3.定义相关变量

这里我对时分秒分别定义了变量进行控制处理,主要是用来根据当前时间来计算指针所需要划过的角度。

/**
 * 0-11
 */
private int mCurrentHour = 0;
/**
 * 0-59
 */
private int mCurrentMinute = 0;
/**
 * 0-59
 */
private int mCurrentSecond = 0;

OK,接下来上核心代码,也就是绘制部分。为了方便计算,我们把画笔移动到正中央,也就是先调用一下canvas.translate(getWidth() / 2, getHeight() / 2);然后再进行绘制。

4.绘制时钟基本元素

/**
 * 绘制时钟基本的元素
 *
 * @param canvas
 */
private void drawClock(Canvas canvas) {
    //绘制圆圈背景
    canvas.drawCircle(0, 0, (getWidth() - dip2px(mContext, 2)) / 2, mPaintBg);
    //绘制文字和小圆点
    for (int i = 0; i < 12; i++) {
        float r = getWidth() / 2 - offset;
        float deg = i * 30 + 30;
        float x = (float) (r * Math.sin(Math.PI * deg / 180));
        float y = (float) (r * Math.cos(Math.PI * deg / 180));
        canvas.drawCircle(x, -y, dip2px(mContext, 3), mPaint);
        //绘制进度数字
        String text = (i + 1) + "";
        //获取文字宽度
        float textWidth = mPaintText.measureText(text, 0, text.length());
        float dx = x - x / 10 - textWidth / 2;
        Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
        float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
        float baseLine = -(y - y / 10) + dy;
        canvas.drawText(text, dx, baseLine, mPaintText);
    }
}

到这里我们就能看到基本的样子已经出来了,如下图:


5.绘制时针

/**
 * 绘制时针
 *
 * @param canvas
 */
private void drawHour(Canvas canvas) {
    mPaintLine.setColor(Color.BLACK);
    /**
     * 找到时间和弧度的关系,先计算出总的时间加在一起是多少个小时,
     * 然后根据小时数计算出在时钟上需要绘制的角度是多少,再求出坐标进行绘制
     */
    float hour = (float) mCurrentHour + (float) mCurrentMinute / 60 + (float) mCurrentSecond / 3600;
    float deg1 = hour * 30;
    Log.i(TAG, "度数deg1:" + hour);
    float x1 = (float) (mDistanceHour * Math.sin(Math.PI * deg1 / 180));
    float y1 = (float) (mDistanceHour * Math.cos(Math.PI * deg1 / 180));
    //canvas.drawCircle(x1, -y1, 10, mPaintLine);
    canvas.drawLine(0, 0, x1, -y1, mPaintLine);
}

6.绘制分针

/**
 * 绘制分针
 *
 * @param canvas
 */
private void drawMinute(Canvas canvas) {
    mPaintLine.setColor(Color.BLACK);
    /**
     * 找到时间和弧度的关系
     */
    float minute = (float) mCurrentMinute + (float) mCurrentSecond / 60;
    float deg1 = minute * 6;
    Log.i(TAG, "===度数minute:" + minute + "===度数deg1:" + deg1);
    float x1 = (float) (mDistanceMinute * Math.sin(Math.PI * deg1 / 180));
    float y1 = (float) (mDistanceMinute * Math.cos(Math.PI * deg1 / 180));
    //canvas.drawCircle(x1, -y1, 10, mPaintLine);
    canvas.drawLine(0, 0, x1, -y1, mPaintLine);
}
7.绘制秒针

/**
 * 绘制秒针
 *
 * @param canvas
 */
private void drawSecond(Canvas canvas) {
    mPaintLine.setColor(Color.RED);
    /**
     * 找到时间和弧度的关系
     */
    float deg1 = mCurrentSecond * 6;
    Log.i(TAG, "===度数deg1:" + deg1);
    float x1 = (float) (mDistanceSecond * Math.sin(Math.PI * deg1 / 180));
    float y1 = (float) (mDistanceSecond * Math.cos(Math.PI * deg1 / 180));
    //canvas.drawCircle(x1, -y1, 10, mPaintLine);
    canvas.drawLine(0, 0, x1, -y1, mPaintLine);
}
8.动起来

如何动起来,就很简单了,我们只要每隔1秒进行绘制一次即可,这里我使用了Handler+Runnable的方式进行实现的,代码很简单就不单独列出来了,下面直接上完整代码。

ClockView.java

/**
 * 自定义View系列-时钟Clock
 */
public class ClockView extends View {

    private static final String TAG = ClockView.class.getSimpleName();

    /**
     * 上下文
     */
    private Context mContext;
    /**
     * 背景绘制画笔
     */
    private Paint mPaintBg;
    /**
     * 时钟元素画笔
     */
    private Paint mPaint;
    /**
     * 绘制指针的画笔
     */
    private Paint mPaintLine;
    /**
     * 绘制数字的画笔
     */
    private Paint mPaintText;
    /**
     * 0-11
     */
    private int mCurrentHour = 0;
    /**
     * 0-59
     */
    private int mCurrentMinute = 0;
    /**
     * 0-59
     */
    private int mCurrentSecond = 0;

    /**
     * 指针长度-时针
     */
    private float mDistanceHour;
    /**
     * 指针长度-分针
     */
    private float mDistanceMinute;
    /**
     * 指针长度-秒针
     */
    private float mDistanceSecond;
    /**
     * 偏移量
     */
    private float offset;
    /**
     * 时间间隔
     */
    private int TIME = 1000;//默认每隔10ms重绘一次
    /**
     * Handler
     */
    Handler handler = new Handler();
    /**
     * Runnable
     */
    Runnable runnable = new Runnable() {

        @Override
        public void run() {
            try {
                handler.postDelayed(this, TIME);
                switch (mTypeModel) {
                    case CURRENT_MODEL:
                        getCurrentTime();
                        break;
                    case DEFAULT_MODEL:
                        getDefaultTime();
                        break;
                }
                invalidate();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    };

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

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

    public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
        initView();
        offset = dip2px(mContext, 10);
    }

    /**
     * 初始化
     */
    private void initView() {
        mPaintBg = new Paint();
        mPaintBg.setAntiAlias(true);
        mPaintBg.setColor(Color.BLACK);
        mPaintBg.setStrokeWidth(dip2px(mContext, 2));
        mPaintBg.setStyle(Paint.Style.STROKE);

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);

        mPaintLine = new Paint();
        mPaintLine.setAntiAlias(true);
        mPaintLine.setStyle(Paint.Style.FILL);
        mPaintLine.setStrokeWidth(dip2px(mContext, 2));

        mPaintText = new Paint();
        mPaintText.setAntiAlias(true);
        mPaintText.setColor(Color.BLACK);
        mPaintText.setStyle(Paint.Style.STROKE);
        mPaintText.setTextSize(30);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mDistanceHour = w / 5 - dip2px(mContext, 7);
        mDistanceMinute = mDistanceHour + dip2px(mContext, 18);
        mDistanceSecond = mDistanceHour + dip2px(mContext, 45);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(getWidth() / 2, getHeight() / 2);
        drawClock(canvas);
        drawHour(canvas);
        drawMinute(canvas);
        drawSecond(canvas);

        /**
         * 绘制中心点
         */
        canvas.drawCircle(0, 0, 20, mPaint);
    }

    /**
     * 绘制时钟基本的元素
     *
     * @param canvas
     */
    private void drawClock(Canvas canvas) {
        //绘制圆圈背景
        canvas.drawCircle(0, 0, (getWidth() - dip2px(mContext, 2)) / 2, mPaintBg);
        //绘制文字和小圆点
        for (int i = 0; i < 12; i++) {

            float r = getWidth() / 2 - offset;
            float deg = i * 30 + 30;
            float x = (float) (r * Math.sin(Math.PI * deg / 180));
            float y = (float) (r * Math.cos(Math.PI * deg / 180));

            canvas.drawCircle(x, -y, dip2px(mContext, 3), mPaint);

            //绘制进度数字
            String text = (i + 1) + "";
            //获取文字宽度
            float textWidth = mPaintText.measureText(text, 0, text.length());
            float dx = x - x / 10 - textWidth / 2;
            Paint.FontMetricsInt fontMetricsInt = mPaintText.getFontMetricsInt();
            float dy = (fontMetricsInt.bottom - fontMetricsInt.top) / 2 - fontMetricsInt.bottom;
            float baseLine = -(y - y / 10) + dy;
            canvas.drawText(text, dx, baseLine, mPaintText);
        }
    }


    /**
     * 绘制时针
     *
     * @param canvas
     */
    private void drawHour(Canvas canvas) {
        mPaintLine.setColor(Color.BLACK);
        /**
         * 找到时间和弧度的关系,先计算出总的时间加在一起是多少个小时,
         * 然后根据小时数计算出在时钟上需要绘制的角度是多少,再求出坐标进行绘制
         */
        float hour = (float) mCurrentHour + (float) mCurrentMinute / 60 + (float) mCurrentSecond / 3600;
        float deg1 = hour * 30;
        Log.i(TAG, "度数deg1:" + hour);
        float x1 = (float) (mDistanceHour * Math.sin(Math.PI * deg1 / 180));
        float y1 = (float) (mDistanceHour * Math.cos(Math.PI * deg1 / 180));
        //canvas.drawCircle(x1, -y1, 10, mPaintLine);
        canvas.drawLine(0, 0, x1, -y1, mPaintLine);
    }


    /**
     * 绘制分针
     *
     * @param canvas
     */
    private void drawMinute(Canvas canvas) {
        mPaintLine.setColor(Color.BLACK);
        /**
         * 找到时间和弧度的关系
         */
        float minute = (float) mCurrentMinute + (float) mCurrentSecond / 60;
        float deg1 = minute * 6;
        Log.i(TAG, "===度数minute:" + minute + "===度数deg1:" + deg1);
        float x1 = (float) (mDistanceMinute * Math.sin(Math.PI * deg1 / 180));
        float y1 = (float) (mDistanceMinute * Math.cos(Math.PI * deg1 / 180));
        //canvas.drawCircle(x1, -y1, 10, mPaintLine);
        canvas.drawLine(0, 0, x1, -y1, mPaintLine);
    }

    /**
     * 绘制秒针
     *
     * @param canvas
     */
    private void drawSecond(Canvas canvas) {
        mPaintLine.setColor(Color.RED);
        /**
         * 找到时间和弧度的关系
         */
        float deg1 = mCurrentSecond * 6;
        Log.i(TAG, "===度数deg1:" + deg1);
        float x1 = (float) (mDistanceSecond * Math.sin(Math.PI * deg1 / 180));
        float y1 = (float) (mDistanceSecond * Math.cos(Math.PI * deg1 / 180));
        //canvas.drawCircle(x1, -y1, 10, mPaintLine);
        canvas.drawLine(0, 0, x1, -y1, mPaintLine);
    }


    /**
     * 开始
     */
    public void start() {
        stop();
        handler.postDelayed(runnable, TIME); //每隔TIMEms执行
    }

    public void stop() {
        try {
            handler.removeCallbacks(runnable);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 获取当前时间
     */
    private void getCurrentTime() {
        Calendar now = Calendar.getInstance();
        mCurrentHour = now.get(Calendar.HOUR_OF_DAY);
        mCurrentMinute = now.get(Calendar.MINUTE);
        mCurrentSecond = now.get(Calendar.SECOND);
    }

    private long timeSeconds = 32353450;

    private void getDefaultTime() {
        timeSeconds++;
        mCurrentHour = (int) (timeSeconds / 60 / 60 % 60);
        mCurrentMinute = (int) (timeSeconds / 60 % 60);
        mCurrentSecond = (int) (timeSeconds % 60);
    }

    private TYPE_MODEL mTypeModel = TYPE_MODEL.DEFAULT_MODEL;

    public enum TYPE_MODEL {
        CURRENT_MODEL, DEFAULT_MODEL;
    }

    public void startSpeed() {
        mTypeModel = TYPE_MODEL.DEFAULT_MODEL;
        TIME = 1;
    }

    public void defaultModel() {
        TIME = 1000;
        mTypeModel = TYPE_MODEL.CURRENT_MODEL;
    }


    /**
     * 根据手机的分辨率从 dp 的单位 转成为 px(像素)
     */
    public static int dip2px(Context context, float dpValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

}

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private ClockView mClockView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mClockView = findViewById(R.id.view);
        mClockView.start();
    }

    /**
     * 加快版
     *
     * @param view
     */
    public void clickSpeed(View view) {
        mClockView.startSpeed();
    }

    /**
     * 当前时间
     *
     * @param view
     */
    public void clickDefault(View view) {
        mClockView.defaultModel();
    }

}

OK,结束,打完收工!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容