Android笔记自定义View之制作表盘界面

前言

最近我跟自定义View杠上了,甚至说有点上瘾到走火入魔了。身为菜鸟的我自然要查阅大量的资料,学习大神们的代码,这不,前两天正好在郭神在微信公众号里推送一片自定义控件的文章——一步步实现精美的钟表界面。正适合我这种菜鸟来学习,闲着没事,我就差不多依葫芦画瓢也写了一个自定义表盘View,现在纯粹最为笔记记录下来。先展示下效果图:

下面进入正题

自定义表盘属性

老规矩,先在attrs文件里添加表盘自定义属性

    <declare-styleable name="WatchView">
        <attr name="watchRadius" format="dimension"/>                     //表盘半径
        <attr name="watchPadding" format="dimension"/>                    //表盘相对控件边框距离
        <attr name="watchScalePadding" format="dimension"/>               //刻度相对表盘距离
        <attr name="watchScaleColor" format="color|reference"/>           //常规刻度颜色
        <attr name="watchScaleLength" format="dimension|reference"/>      //常规刻度长度
        <attr name="watchHourScaleColor" format="dimension|reference"/>   //整点刻度颜色
        <attr name="watchHourScaleLength" format="dimension|reference"/>  //整点刻度长度
        <attr name="hourPointColor" format="color|reference"/>            //时针颜色
        <attr name="hourPointLength" format="dimension|reference"/>       //时针长度
        <attr name="minutePointColor" format="color|reference"/>          //分针颜色
        <attr name="minutePointLength" format="dimension|reference"/>     //分针长度
        <attr name="secondPointColor" format="color|reference"/>          //秒针颜色
        <attr name="secondPointLength" format="dimension|reference"/>     //秒针长度
        <attr name="timeTextSize" format="dimension|reference"/>          //表盘字体大小
        <attr name="timeTextColor" format="color|reference"/>             //表盘字体颜色
    </declare-styleable>

在自定义View的构造方法种获取自定义属性

先将属性变量声明如下:

<span style="font-size:14px;">    /**表盘边距*/
    private float mWatchPadding = 5;
    /**表盘与刻度边距*/
    private float mWatchScalePadding = 5;
    /**表盘半径*/
    private float mWatchRadius = 250;
    /**表盘刻度长度*/
    private float mWatchScaleLength;
    /**表盘刻度颜色*/
    private int mWatchScaleColor = Color.BLACK;
    /**表盘整点刻度长度*/
    private float mHourScaleLength = 8;
    /**表盘整点刻度颜色*/
    private int mHourScaleColor = Color.BLUE;
    /**表盘时针颜色*/
    private int mHourPointColor = Color.BLACK;
    /**表盘时针长度*/
    private float mHourPointLength = 100;
    /**表盘分针颜色*/
    private int mMinutePointColor = Color.BLACK;
    /**表盘分针长度*/
    private float mMinutePointLength = 130;
    /**表盘秒针颜色*/
    private int mSecondPointColor = Color.RED;
    /**表盘秒针长度*/
    private float mSecondPointLength = 160;
    /**表盘尾部指针长度*/
    private float mEndPointLength;
    /**表盘数字颜色*/
    private int mTimeTextColor = Color.BLACK;
    /**表盘数字大小*/
    private int mTimeTextSize = 15;</span>

在构造方法种获取自定义属性

<span style="font-size:14px;">    public WatchView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.WatchView);
        int n = array.getIndexCount();
        for (int i = 0;i<n;i++){
            int attr = array.getIndex(i);
            switch (attr){
                case R.styleable.WatchView_watchRadius:
                    mWatchRadius = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,60));
                    break;
                case R.styleable.WatchView_watchPadding:
                    mWatchPadding = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,5));
                    break;
                case R.styleable.WatchView_watchScalePadding:
                    mWatchScalePadding = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,3));
                    break;
                case R.styleable.WatchView_watchScaleLength:
                    mWatchScaleLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,5));
                    break;
                case R.styleable.WatchView_watchScaleColor:
                    mWatchScaleColor = array.getColor(attr, Color.parseColor("#50000000"));
                    break;
                case R.styleable.WatchView_watchHourScaleLength:
                    mHourScaleLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,10));
                    break;
                case R.styleable.WatchView_watchHourScaleColor:
                    mHourScaleColor = array.getColor(attr,Color.BLACK);
                    break;
                case R.styleable.WatchView_hourPointLength:
                    mHourPointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,35));
                    break;
                case R.styleable.WatchView_hourPointColor:
                    mHourPointColor = array.getColor(attr,Color.BLACK);
                    break;
                case R.styleable.WatchView_minutePointLength:
                    mMinutePointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,40));
                    break;
                case R.styleable.WatchView_minutePointColor:
                    mMinutePointColor = array.getColor(attr,Color.BLACK);
                    break;
                case R.styleable.WatchView_secondPointLength:
                    mSecondPointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,50));
                    break;
                case R.styleable.WatchView_secondPointColor:
                    mSecondPointColor = array.getColor(attr,Color.BLUE);
                    break;
                case R.styleable.WatchView_timeTextSize:
                    mTimeTextSize = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,15));
                    break;
                case R.styleable.WatchView_timeTextColor:
                    mTimeTextColor = array.getColor(attr,Color.BLACK);
                    break;
            }

        }
        array.recycle();
    }</span>

设置控件大小

这里当然就是重写onMeasure方法啦,这里我们处理的简单点,如下面代码所示,当我们将控件的宽高都设定为wrap_content(即MeasureSpec.UNSPECIFED)时,我们将宽高设定为默认值(wrapContentSize)和圆盘半径+圆盘边距(mWatchRadius+mWatchPadding)之间取最大值,其他情况下就取系统自取值。当然作为一个严谨的控件,仅仅这样处理肯定是不行的。项目中,我们要根据我们的需求自行修改里面的代码以适配。

<span style="font-size:14px;">    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int wrapContentSize = 1000;
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (widthMode == MeasureSpec.UNSPECIFIED && heightMode == MeasureSpec.UNSPECIFIED){
            wrapContentSize = (int) Math.max(wrapContentSize,mWatchRadius+mWatchPadding);
            setMeasuredDimension(wrapContentSize,wrapContentSize);
        }else {
            setMeasuredDimension(widthSize,heightSize);
        }
    }</span>

重写onDraw方法

来到最关键真正画表盘时刻了。一步一步来,首先初始化我们的画笔(我的习惯,写一个initPaint方法)

<span style="font-size:14px;">    private void initPaint(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.FILL);
    }</span>

为了不显赘述,方便理解,我直接展示代码,在代码中解释

开画之前我们先将画笔移动到控件中心点位置,如下:

<span style="font-size:14px;">@Override
    protected void onDraw(Canvas canvas) {
        canvas.translate(getWidth()/2,getHeight()/2);
    }</span>

第一步,画表盘

<span style="font-size:14px;">    /**
     * 画表盘
     * @param canvas
     */
    private void paintWatchBoard(Canvas canvas){
        initPaint();
        canvas.save();
        canvas.drawCircle(0,0,mWatchRadius,mPaint); //画圆盘
        canvas.restore();
    }</span>

注:每次画图之前都要先调用canvas.save()方法,保存画笔属性,画完之后要调用canvas.restore()方法,重置画笔属性

这里就不一一展示每次画完之后的效果图了。

第二步,画刻度+整点时间数字(刻度从12点方向开始画)

<span style="font-size:14px;">    /**
     * 画刻度及整点数字
     * @param canvas
     */
    private void paintScale(Canvas canvas){
        int lineLength; //刻度线长度
        canvas.save();
        for (int i = 0;i<60;i++){
            if (i%5 == 0){//整点刻度下画笔相关属性
                mPaint.setStrokeWidth(MyUtil.dip2px(getContext(),1.5f));
                mPaint.setColor(mHourScaleColor);
                lineLength = MyUtil.dip2px(getContext(),8);
                canvas.drawLine(0,-mWatchRadius+mWatchScalePadding,0,-mWatchRadius+mWatchScalePadding+lineLength,mPaint);
                mPaint.setColor(mTimeTextColor);
                mPaint.setTextSize(mTimeTextSize);
                canvas.drawText(mTimes[i/5],-mTimeTextSize/2,-mWatchRadius+mWatchScalePadding + lineLength+mTimeTextSize,mPaint);//整点的位置标上整点时间数字
            }else {//非整点刻度下画笔相关属性
                mPaint.setStrokeWidth(MyUtil.dip2px(getContext(),0.8f));
                mPaint.setColor(mWatchScaleColor);
                lineLength = MyUtil.dip2px(getContext(),5);
                canvas.drawLine(0,-mWatchRadius+mWatchScalePadding,0,-mWatchRadius+mWatchScalePadding+lineLength,mPaint);
            }
            canvas.rotate(6);//每次画完一个刻度线,画笔顺时针旋转6度(360/60,相邻两刻度之间的角度差为6度)
        }
        canvas.restore();
    }</span>

其中,整点数字我用了罗马数字来表示

<span style="font-size:14px;">private String[] mTimes = {"XII","Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","XI"};</span>

第三步,画时针、分针、秒针以及其它修饰图

考虑到时针、分针和秒针大小长度各不一样,我这里特意定义了三支画笔来分别画时针、分针和秒针。

同样的,先初始化指针画笔:

<span style="font-size:14px;">/**
     * 初始化指针
     */
    private void initPointPaint(){
        mHourPaint = new Paint();
        mHourPaint.setAntiAlias(true);
        mHourPaint.setStyle(Paint.Style.FILL);
        mHourPaint.setStrokeWidth(16);
        mHourPaint.setColor(mHourPointColor);

        mMinutePaint = new Paint();
        mMinutePaint.set(mHourPaint);
        mMinutePaint.setStrokeWidth(12);
        mMinutePaint.setColor(mMinutePointColor);

        mSecondPaint = new Paint();
        mSecondPaint.set(mHourPaint);
        mSecondPaint.setStrokeWidth(7);
        mSecondPaint.setColor(mSecondPointColor);
        mEndPointLength = mWatchRadius/6; //(修饰部分)指针尾部长度,定义为表盘半径的六分之一
    }</span>

画指针

<span style="font-size:14px;">/**
     * 画指针
     * @param canvas
     */
    private void paintPoint(Canvas canvas){
        initPointPaint();
        Calendar c = Calendar.getInstance(); //取当前时间
        int hour = c.get(Calendar.HOUR_OF_DAY);
        int minute = c.get(Calendar.MINUTE);
        int second = c.get(Calendar.SECOND);
        //绘制时针
        canvas.save();
        canvas.rotate(hour*30);
        canvas.drawLine(0,0,0,-mHourPointLength,mHourPaint);
        canvas.drawLine(0,0,0,mEndPointLength,mHourPaint);
        canvas.restore();
        //绘制分针
        canvas.save();
        canvas.rotate(minute*6);
        canvas.drawLine(0,0,0,-mMinutePointLength,mMinutePaint);
        canvas.drawLine(0,0,0,mEndPointLength,mMinutePaint);
        canvas.restore();
        //绘制秒针
        canvas.save();
        canvas.rotate(second*6);
        canvas.drawLine(0,0,0,-mSecondPointLength,mSecondPaint);
        canvas.drawLine(0,0,0,mEndPointLength,mSecondPaint);
        canvas.restore();
    }</span>

OK,该有的差不多都有了,直接在onDraw中调用吧

<span style="font-size:14px;">@Override
    protected void onDraw(Canvas canvas) {
        canvas.translate(getWidth()/2,getHeight()/2);
        paintWatchBoard(canvas); //画表盘
        paintScale(canvas); //画刻度
        paintPoint(canvas); //画指针
        canvas.drawCircle(0,0,15,mSecondPaint); //为了美观,也让表盘更接近我们显示生活中的样子,我在圆盘中心画了一个大红圆点装饰秒针
        postInvalidateDelayed(1000); //每隔一秒钟画一次
    }</span>

(⊙v⊙)嗯,自定义View大功告成,我们在布局文件里调用看下效果吧

<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:zhusp="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorAccent">

    <com.wondertek.propertyanimatordemo.WatchView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        zhusp:timeTextSize="20dp"
        zhusp:watchRadius="150dp"
        zhusp:hourPointLength="80dp"
        zhusp:minutePointLength="100dp"
        zhusp:secondPointLength="115dp"/>
</RelativeLayout></span>

最后我这里的静态效果是这样的:

尾声

写完了,最后来加点逼格,学无止境,循序渐进。既然选择了远方,便只顾风雨兼程

参考资料:自定义View新手实战-一步步实现精美的钟表界面

时间: 2024-08-15 10:14:14

Android笔记自定义View之制作表盘界面的相关文章

Android中自定义View的MeasureSpec使用

有时,Android系统控件无法满足我们的需求,因此有必要自定义View.具体方法参见官方开发文档:http://developer.android.com/guide/topics/ui/custom-components.html 一般来说,自定义控件都会去重写View的onMeasure方法,因为该方法指定该控件在屏幕上的大小. protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) onMeasure传

android中自定义view涉及到的绘制知识

android中自定义view的过程中,需要了解的绘制知识. 1.画笔paint: 画笔设置: <span style="font-size:14px;"> paint.setAntiAlias(true);//抗锯齿功能 paint.setColor(Color.RED); //设置画笔颜色 paint.setStyle(Style.FILL);//设置填充样式 paint.setStrokeWidth(30);//设置画笔宽度 paint.setShadowLayer(

Android进阶——自定义View之自己绘制彩虹圆环调色板

引言 前面几篇文章都是关于通过继承系统View和组合现有View来实现自定义View的,刚好由于项目需要实现一个滑动切换LED彩灯颜色的功能,所以需要一个类似调色板的功能,随着手在调色板有效区域滑动,LED彩灯随即显示相应的颜色,也可以通过左右的按钮,按顺序切换显示一组颜色,同时都随着亮度的改变LED彩灯的亮度随即变化,这篇基本上把继承View重绘实现自定义控件的大部分知识总结了下(当然还有蛮多没有涉及到,比如说自适应布局等),源码在Github上 一.继承View绘制自定义控件的通用步骤 自定

Android 开发自定义View

作者:卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/41551151 [附:--自定义View常处理的回调函数 onFinishInflate() 当View中所有的子控件均被映射成xml后触发 onMeasure(int, int) 确定所有子元素的大小 onLayout(boolean, int, int, int, int) 当View分配所有的子元素的大小和位置时触发 onSizeChanged(int, int, int

Android应用自定义View绘制方法手册

背景 这篇迟迟难产的文章算是对2015前半年的一个交代吧,那时候有一哥们要求来一发Android Canvas相关总结,这哥们还打赏了,实在不好意思,可是这事一放就给放忘了,最近群里小伙伴催着说没更新博客,坐等更新啥的,随先有这么一篇Android应用开发超级基础的文章诞生了(因为这种文章最好写哈,就是用熟了就行).不得不说下这么久为何一直没更新博客的原因了,首先遇上了过年,我个人崇尚过节就该放下一切好好陪陪亲人,珍惜在一起的时光:其次今年开年很是蛋疼,不是不顺当就是深深的觉得被坑,所以心情也就

Android之自定义View的实现

对于学习Android开发的小童鞋对于自定义View一定不会陌生,相信大家对它是又爱又恨,爱它可以跟随我们的心意设计出漂亮的效果:恨它想要完全流畅掌握,需要一定的功夫.对于初学者来说确实很不容易,网上有很多相关的博客文档之类的资料,但是往往对于初学者,只知拷贝修改,不理解其中的深意,最后还是无法更具自己的需要,进行自定义的开发,本篇我们就一同来了解下自定义View的神秘面纱. 开发自定义控件的步骤: 1.了解View的工作原理 2. 编写继承自View的子类 3. 为自定义View类增加属性 4

Android进阶——自定义View之扩展系统Dialog

引言 今天给大家总结有关自定义对话框的相关内容,前面文章Android入门--AlertDialog和ProgressDialog总结都在在利用系统提供的函数来实现对话框,但局限性太大,当我们想自己定义Dialog视图的时候,就不能利用系统函数了,就需要我们这里的自定义对话框了来满足产品经理的各种idea. 一.Dialog部分源码结构 学习下源码的编程风格和规范 /** * Base class for Dialogs. * Activity提供了一系列的方法用于dialog的管理:onCre

Android开发自定义View

Android中View组件的作用类似于Swing变成中的JPanel,它只是一个空白的矩形区域,View组件中没有任何内容.对于Android应用的其他UI组件来说,它们都继承了View组件,然后在View组件提供的空白区域绘制外观. 当Android系统提供的UI组件不足以满足项目需求时,我们可以通过继承View并重写View类的一个或多个方法来自定义组件. 通常可以被用户重写的方法如下: 1.构造器:重写构造器是定制View的最基本的方式,当Java(或Kotlin)代码创建一个View实

Android 创建自定义 View 的属性 (attrs) 时需要注意的问题

自定义 View 的属性并不难,可以参照官方的文档 https://developer.android.com/training/custom-views/create-view.html 但是需要注意一个问题,否则可能浪费很多时间. <resources> <declare-styleable name="AppsControllerBlock"> <attr name="letterCase" format="enum&q