Android自定义一个属于自己的时间钟表

简介: Android自定义一个属于自己的时间钟表

1、概述

本文主要讲解的是如何自定义一个时间钟表,通过简单的练习可以简单学习android当中自定义view的一些常用绘图技巧,优化android绘图操作。言归正传,首先看下我们需要实现的效果:

当我们看到这个效果的时候脑子里应该有一定的思路了,我们应该把它分解成以下几个步骤:


1、仪表盘(圆)


2、刻度线(长 中 短)


3、刻度值(1-12)


4、指针(时  分  秒)


5、移动指针,计算指针位置


现在我们已经很清楚自己的思路了,那么我们一个一个来。



第一步:1、自定义View的属性,首先在res/values/  下建立一个attrs.xml , 在里面定义我们的属性和声明我们的整个样式。

    <declare-styleable name="ClockView">
 
        <attr name="mRadius" format="dimension"/>
        <attr name="mCircleColor" format="color"/>
        <attr name="mCircleWidth" format="dimension"/>
        <attr name="mTextSize" format="dimension"/>
        <attr name="mTextColor" format="color"/>
        <attr name="mBigScaleColor" format="color"/>
        <attr name="mMiddlecaleColor" format="color"/>
        <attr name="mSmallScaleColor" format="color"/>
        <attr name="mHourHandColor" format="color"/>
        <attr name="mMinuteHandColor" format="color"/>
        <attr name="mSecondHandColor" format="color"/>
        <attr name="mHourHandWidth" format="dimension"/>
        <attr name="mMinuteHandWidth" format="dimension"/>
        <attr name="mSecondHandWidth" format="dimension"/>
 
    </declare-styleable>

我们定义了钟表的半径,背景颜色 ,刻度值(大,中,小)的颜色及指针(时分秒)的颜色和宽度。


然后自定义一个class类 为ClockView,在MainActivity的布局中引用:

  <com.dalong.customviewstudy.view.ClockView
      app:mSecondHandColor="@color/colorAccent"
      app:mCircleColor="@android:color/white"
      app:mBigScaleColor="@android:color/black"
      app:mMiddlecaleColor="@android:color/black"
      app:mSmallScaleColor="@color/colorAccent"
      app:mHourHandColor="@android:color/black"
      app:mMinuteHandColor="@android:color/black"
      app:mTextColor="@android:color/black"
      app:mHourHandWidth="13dp"
      app:mSecondHandWidth="5dp"
      app:mMinuteHandWidth="8dp"
      app:mTextSize="16sp"
      android:layout_centerInParent="true"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />

2、在自定义View的构造方法中,获得我们的自定义的样式


 //文字画笔对象
    private  Paint mTextPaint;
 
    //圆,指针,刻度画笔
    private  Paint mPaint;
 
    //半径
    public float mRadius;
 
    //外圆的颜色
    public int mCircleColor;
 
    // 外圆的宽度
    public float mCircleWidth;
 
    //文字的大小
    public float mTextSize;
 
    //文字的颜色
    public int mTextColor;
 
    //大刻度颜色
    public int mBigScaleColor;
 
    //中刻度
    public int mMiddlecaleColor;
 
    //小刻度颜色
    public int mSmallScaleColor;
 
    //时针颜色
    public int mHourHandColor;
 
    //分针颜色
    public int mMinuteHandColor;
 
    //秒针颜色
    public int mSecondHandColor;
 
    //时针宽度
    public float mHourHandWidth;
 
    //分针宽度
    public float mMinuteHandWidth;
 
    //秒针宽度
    public float mSecondHandWidth;
 
    //控件宽度
    public int mWidth;
 
    //控件高度
    public int mHeght;
 
    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);
 
        TypedArray typedArray=context.obtainStyledAttributes(attrs, R.styleable.ClockView);
        mRadius=typedArray.getDimension(R.styleable.ClockView_mRadius,400);
        mCircleColor=typedArray.getColor(R.styleable.ClockView_mCircleColor, Color.WHITE);
        mCircleWidth=typedArray.getDimension(R.styleable.ClockView_mCircleWidth,20);
        mTextSize=typedArray.getDimension(R.styleable.ClockView_mCircleWidth,40);
        mTextColor=typedArray.getColor(R.styleable.ClockView_mTextColor,Color.DKGRAY);
        mBigScaleColor=typedArray.getColor(R.styleable.ClockView_mBigScaleColor,Color.BLACK);
        mSmallScaleColor=typedArray.getColor(R.styleable.ClockView_mSmallScaleColor,Color.RED);
        mMiddlecaleColor=typedArray.getColor(R.styleable.ClockView_mMiddlecaleColor,Color.BLACK);
        mHourHandColor=typedArray.getColor(R.styleable.ClockView_mHourHandColor,Color.BLACK);
        mMinuteHandColor=typedArray.getColor(R.styleable.ClockView_mMinuteHandColor,Color.BLACK);
        mSecondHandColor=typedArray.getColor(R.styleable.ClockView_mSecondHandColor,Color.BLACK);
        mHourHandWidth=typedArray.getDimension(R.styleable.ClockView_mHourHandWidth,20);
        mMinuteHandWidth=typedArray.getDimension(R.styleable.ClockView_mMinuteHandWidth,10);
        mSecondHandWidth=typedArray.getDimension(R.styleable.ClockView_mSecondHandWidth,5);
 
        mPaint=new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.STROKE);
 
        mTextPaint=new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);
 
    }

3、我们重写onDraw,onMesure调用系统提供的:


onMeure方法

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(measureSize(widthMeasureSpec),measureSize(heightMeasureSpec));
    }
 
    private int measureSize(int mMeasureSpec) {
        int result;
        int mode=MeasureSpec.getMode(mMeasureSpec);
        int size=MeasureSpec.getSize(mMeasureSpec);
        if(mode==MeasureSpec.EXACTLY){
            result=size;
        }else{
            result=400;
            if(mode==MeasureSpec.AT_MOST){
                result=Math.min(result,size);
            }
        }
        return  result;
    }

onDraw方法


 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置宽高、半径
        mWidth=getMeasuredWidth()-getPaddingLeft()-getPaddingRight();
        mHeght=getMeasuredHeight()-getPaddingBottom()-getPaddingTop();
        mRadius=Math.min(mWidth/2,mHeght/2);
        //首先绘制圆
        drawCircle(canvas);
        //绘制刻度
        drawScale(canvas);
        //绘制指针
        drawPointer(canvas);
        //发送消息刷新ui
        handler.sendEmptyMessageDelayed(START_CLOCK,1000);
    }

其中最核心的代码就是这三个方法:

       //首先绘制圆
        drawCircle(canvas);
        //绘制刻度
        drawScale(canvas);
        //绘制指针
        drawPointer(canvas);

首先讲第一个方法:

 /**
     * 画圆
     * @param canvas
     */
    private void drawCircle(Canvas canvas) {
        mPaint.setStrokeWidth(mCircleWidth);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mCircleColor);
        canvas.drawCircle(mWidth/2,mHeght/2,mRadius,mPaint);
    }

这个方法其实很简单给我们的画笔设置我们自定义的样式之后取中心为圆心,以我们设定的半径画圆,这里设置的是Paint.Style.FILL一个实心的。也可以设置一个空心的。看下我们执行这个方法后的效果:

第二方法:

 /**
     * 刻度和文字
     * @param canvas
     */
    private void drawScale(Canvas canvas) {
 
        for (int i=0;i<60;i++){
            //设置大刻度
            if(i==0||i==15||i==30||i==45){
                mPaint.setStrokeWidth(6);
                mPaint.setColor(mBigScaleColor);
                canvas.drawLine(mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2,
                        mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2+60,mPaint);
                String scaleTv=String.valueOf(i==0?12:i/5);
                canvas.drawText(scaleTv,mWidth/2-mTextPaint.measureText(scaleTv)/2,
                        mHeght/2-mWidth/2+mCircleWidth/2+95,mTextPaint);
            }else if (i==5||i==10||i==20||i==25||i==35||i==40||i==50||i==55)
            //设置中刻度
            {
                mPaint.setStrokeWidth(4);
                mPaint.setColor(mMiddlecaleColor);
                canvas.drawLine(mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2,
                        mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2+40,mPaint);
                String scaleTv=String.valueOf(i/5);
                canvas.drawText(scaleTv,mWidth/2-mTextPaint.measureText(scaleTv)/2,
                        mHeght/2-mWidth/2+mCircleWidth/2+75,mTextPaint);
 
            }else
            //设置小刻度
            {
                mPaint.setColor(mSmallScaleColor);
                mPaint.setStrokeWidth(2);
                canvas.drawLine(mWidth/2,mHeght/2-mWidth/2+mCircleWidth/2,
                        mWidth/2,mHeght/2-mWidth/2+mCircleWidth+30,mPaint);
            }
            canvas.rotate(6,mWidth/2,mHeght/2);
        }
    }

这个方法代码看起来也没有什么主要是把一个圆分成60份,因为我们钟表上是有60个刻度,其中设置了4个大刻度分别为0,15,30,45.分别对应的钟表中12点  3点  6点和9点,如果这个地方你有什么疑惑的吧你可以看看你的手表或者钟表就明白了,同时里面也设置了8个中等刻度分别为5,10,20,25,35,40,50,55为中刻度,其实对应的就是1,2,4,5,7,8,10,11点。这里主要是自己觉得这样分明好看而已,如果没有强迫症的你可以直接设置都是大刻度就可以了。其他的都为小刻度,根据自己在attr设置的颜色和尺寸分别设置画笔paint来绘制就可以了。看下我们的效果变成了这样子:

第三个方法就是绘制指针:

 /**
     * 绘制指针
     * @param canvas
     */
    private void drawPointer(Canvas canvas) {
        Calendar mCalendar=Calendar.getInstance();
        //获取当前小时数
        int hours = mCalendar.get(Calendar.HOUR);
        //获取当前分钟数
        int minutes = mCalendar.get(Calendar.MINUTE);
        //获取当前秒数
        int seconds=mCalendar.get(Calendar.SECOND);
 
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        //绘制时针
        canvas.save();
        mPaint.setColor(mHourHandColor);
        mPaint.setStrokeWidth(mHourHandWidth);
        //这里计算时针需要旋转的角度 实现原理是计算出一共多少分钟除以60计算出真实的小时数(带有小数,为了更加准确计算度数),已知12小时是360度,现在求出了实际小时数比例求出角度
        Float hoursAngle = (hours * 60 + minutes) / 60f / 12f  * 360;
        canvas.rotate(hoursAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth/2f*0.5f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();
 
 
        //绘制分针
        canvas.save();
        mPaint.setColor(mMinuteHandColor);
        mPaint.setStrokeWidth(mMinuteHandWidth);
        //这里计算分针需要旋转的角度  60分钟360度,求出实际分钟数所占的度数
        Float minutesAngle = (minutes*60+seconds) / 60f/ 60f * 360;
        canvas.rotate(minutesAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.7f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();
 
        //绘制中间的圆圈
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(mWidth/2,mHeght/2,20,mPaint);
        canvas.restore();
 
 
        //绘制秒针
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        //这里计算秒针需要旋转的角度  60秒360度,求出实际秒数所占的度数
        Float secondAngle = seconds/60f*360;
        canvas.rotate(secondAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.8f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.2f, mPaint);
        canvas.restore();
 
 
    }

其实这个方法我注释已经写的很详细了,首先我们需要获取到当前的时间,这个大家都是经常写的没啥问题。主要就是如何设定时分秒指针的位置才是关键。这里我使用一个很巧妙的方法,让绘制变得简单了些。

先看下绘制时针:

  //绘制时针
        canvas.save();
        mPaint.setColor(mHourHandColor);
        mPaint.setStrokeWidth(mHourHandWidth);
        //这里计算时针需要旋转的角度 实现原理是计算出一共多少分钟除以60计算出真实的小时数(带有小数,为了更加准确计算度数),已知12小时是360度,现在求出了实际小时数比例求出角度
        Float hoursAngle = (hours * 60 + minutes) / 60f / 12f  * 360;
        canvas.rotate(hoursAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 - mWidth/2f*0.5f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();

前面三行代码就不详细说了,Canvas.save方法作用就是将之前所有已经绘制的图片保存起来。为后续操作在新的图层上操作。和photoshop有点一个意思。大家都知道当我们获取到当前小时数了以后我们就应该直接把时针指到对应的时数上吗?肯定不是吧,比如是3点半时针是指到3与4之间的位置对吧。所以我们这里需要获取到当前的分钟数再加上小时数才是真实的当前小时数(这里其实秒针也需要计算的,但是这里忽略不计了,如果你比我还强迫症的话可以加上)。当我们知道当前实际的小时数的时候,就很简单了,因为我们知道一圈360度平均分了12小时,这个别告诉我不知道啊,要是这个常识都不知道,你去面壁吧,所以只要360/12*真实的小时数就是需要旋转的角度。这么想是不是很简单了。计算出角度以后先吧canvas.rotate旋转这个角度在绘制一个直线就ok了,哈哈哈,是不是so esey,其他分针和秒针一样的道理,这里就不多说了,看代码直接能看懂的。

 
        //绘制分针
        canvas.save();
        mPaint.setColor(mMinuteHandColor);
        mPaint.setStrokeWidth(mMinuteHandWidth);
        //这里计算分针需要旋转的角度  60分钟360度,求出实际分钟数所占的度数
        Float minutesAngle = (minutes*60+seconds) / 60f/ 60f * 360;
        canvas.rotate(minutesAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.7f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.15f, mPaint);
        canvas.restore();
 
        //绘制中间的圆圈
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(mWidth/2,mHeght/2,20,mPaint);
        canvas.restore();
 
 
        //绘制秒针
        canvas.save();
        mPaint.setColor(mSecondHandColor);
        mPaint.setStrokeWidth(mSecondHandWidth);
        mPaint.setStyle(Paint.Style.STROKE);
        //这里计算秒针需要旋转的角度  60秒360度,求出实际秒数所占的度数
        Float secondAngle = seconds/60f*360;
        canvas.rotate(secondAngle, mWidth / 2, mHeght / 2);
        canvas.drawLine(mWidth / 2, mHeght / 2 -  mWidth/2f*0.8f, mWidth / 2, mHeght / 2 +  mWidth/2f*0.2f, mPaint);
        canvas.restore();

其中有个绘制中间的圆圈是我的强迫症所致,觉得中间加个圆圈好看点。执行这个方法后我们的效果就成这样了:


哈哈下面就剩下最后一步了,就是让指针动起来,没错就是动起来,其实大家也在意了我们绘制指针的时候是在方法里直接获取了当前时间设置指针的位置的,所以说只要我们搞个定时器一秒刷新下ui就大功告成了,这里就搞个hander发个空消息。

    private Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case START_CLOCK:
                    //更新时分秒
                    invalidate();
                    //每隔1秒更新一次
                    handler.sendEmptyMessageDelayed(START_CLOCK,1000);
                    break;
            }
 
        }
    };

就成了下面的效果了:


是不是很简单呢?附上github:https://github.com/dalong982242260/CustomViewStudy

相关文章
|
Android开发 UED 计算机视觉
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
本文介绍了一款受游戏“金铲铲之战”启发的Android自定义View——线条等待动画的实现过程。通过将布局分为10份,利用`onSizeChanged`测量最小长度,并借助画笔绘制动态线条,实现渐变伸缩效果。动画逻辑通过四个变量控制线条的增长与回退,最终形成流畅的等待动画。代码中详细展示了画笔初始化、线条绘制及动画更新的核心步骤,并提供完整源码供参考。此动画适用于加载场景,提升用户体验。
861 5
Android自定义view之线条等待动画(灵感来源:金铲铲之战)
|
Android开发
Android自定义view之利用PathEffect实现动态效果
本文介绍如何在Android自定义View中利用`PathEffect`实现动态效果。通过改变偏移量,结合`PathEffect`的子类(如`CornerPathEffect`、`DashPathEffect`、`PathDashPathEffect`等)实现路径绘制的动态变化。文章详细解析了各子类的功能与参数,并通过案例代码展示了如何使用`ComposePathEffect`组合效果,以及通过修改偏移量实现动画。最终效果为一个菱形图案沿路径运动,源码附于文末供参考。
337 0
|
Android开发 开发者
Android自定义view之利用drawArc方法实现动态效果
本文介绍了如何通过Android自定义View实现动态效果,重点使用`drawArc`方法完成圆弧动画。首先通过`onSizeChanged`进行测量,初始化画笔属性,设置圆弧相关参数。核心思路是不断改变圆弧扫过角度`sweepAngle`,并调用`invalidate()`刷新View以实现动态旋转效果。最后附上完整代码与效果图,帮助开发者快速理解并实践这一动画实现方式。
367 0
|
Android开发 数据安全/隐私保护 开发者
Android自定义view之模仿登录界面文本输入框(华为云APP)
本文介绍了一款自定义输入框的实现,包含静态效果、hint值浮动动画及功能扩展。通过组合多个控件完成界面布局,使用TranslateAnimation与AlphaAnimation实现hint文字上下浮动效果,支持密码加密解密显示、去除键盘回车空格输入、光标定位等功能。代码基于Android平台,提供完整源码与attrs配置,方便复用与定制。希望对开发者有所帮助。
373 0
|
XML Java Android开发
Android自定义view之网易云推荐歌单界面
本文详细介绍了如何通过自定义View实现网易云音乐推荐歌单界面的效果。首先,作者自定义了一个圆角图片控件`MellowImageView`,用于绘制圆角矩形图片。接着,通过将布局放入`HorizontalScrollView`中,实现了左右滑动功能,并使用`ViewFlipper`添加图片切换动画效果。文章提供了完整的代码示例,包括XML布局、动画文件和Java代码,最终展示了实现效果。此教程适合想了解自定义View和动画效果的开发者。
621 65
Android自定义view之网易云推荐歌单界面
|
XML 前端开发 Android开发
一篇文章带你走近Android自定义view
这是一篇关于Android自定义View的全面教程,涵盖从基础到进阶的知识点。文章首先讲解了自定义View的必要性及简单实现(如通过三个构造函数解决焦点问题),接着深入探讨Canvas绘图、自定义属性设置、动画实现等内容。还提供了具体案例,如跑马灯、折线图、太极图等。此外,文章详细解析了View绘制流程(measure、layout、draw)和事件分发机制。最后延伸至SurfaceView、GLSurfaceView、SVG动画等高级主题,并附带GitHub案例供实践。适合希望深入理解Android自定义View的开发者学习参考。
1113 84
|
缓存 前端开发 Android开发
安卓开发中的自定义视图:从零到英雄
【10月更文挑战第42天】 在安卓的世界里,自定义视图是一块画布,让开发者能够绘制出独一无二的界面体验。本文将带你走进自定义视图的大门,通过深入浅出的方式,让你从零基础到能够独立设计并实现复杂的自定义组件。我们将探索自定义视图的核心概念、实现步骤,以及如何优化你的视图以提高性能和兼容性。准备好了吗?让我们开始这段创造性的旅程吧!
354 1
|
Android开发 开发者
安卓应用开发中的自定义视图
【9月更文挑战第37天】在安卓开发的海洋中,自定义视图犹如一座座小岛,等待着勇敢的探索者去发现其独特之处。本文将带领你踏上这段旅程,从浅滩走向深海,逐步揭开自定义视图的神秘面纱。
249 3
|
数据可视化 Android开发 开发者
安卓应用开发中的自定义View组件
【10月更文挑战第5天】在安卓应用开发中,自定义View组件是提升用户交互体验的利器。本篇将深入探讨如何从零开始创建自定义View,包括设计理念、实现步骤以及性能优化技巧,帮助开发者打造流畅且富有创意的用户界面。
538 0
|
Android开发 开发者
Android自定义View之不得不知道的文件attrs.xml(自定义属性)
本文详细介绍了如何通过自定义 `attrs.xml` 文件实现 Android 自定义 View 的属性配置。以一个包含 TextView 和 ImageView 的 DemoView 为例,讲解了如何使用自定义属性动态改变文字内容和控制图片显示隐藏。同时,通过设置布尔值和点击事件,实现了图片状态的切换功能。代码中展示了如何在构造函数中解析自定义属性,并通过方法 `setSetting0n` 和 `setbackeguang` 实现功能逻辑的优化与封装。此示例帮助开发者更好地理解自定义 View 的开发流程与 attrs.xml 的实际应用。
565 2
Android自定义View之不得不知道的文件attrs.xml(自定义属性)

热门文章

最新文章