android自定义view实现钟表效果

本文实例为大家分享了android view实现钟表的具体代码,供大家参考,具体内容如下

先看效果图:

自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表

当然绘制之前我们必须进行测量(重写onMeasure),根据自己的规则去测量,这暂时是将控件限制为一个正方形。

首先我们先把钟表分解,看它由哪几部分组成。如上图:钟表包括表盘(刻度)和表针还有文字构成。

分清结构之后我们再明确canvas需要画什么,表盘的构成其实就是外层一个圆,然后上面是有规律的线段,表针就是三个长短不一的线段,再加上12个钟点文字。这样一分析是不是发现调用canvas的drawCircle、drawLine和drawText就可以完成钟表的绘制了。

既然明确了我们绘制所需要的方法,那么就开始重头戏了,告诉canvas在哪绘制这些零件。

最外层的圆是最简单的,我们只需要以控件的中心为圆心,控件的宽度一半为半径画一个圆就可以了。

接下来就是难点一了,这些刻度怎么办呢,其实我们不难发现其中的规律,每个刻度之间的弧度是一样的,那这样我们是不是可以通过旋转画布就可以实现这些刻度的绘制呢,答案是肯定的。

难点二,文字又该如何绘制,难道也通过旋转画布吗,但是你想一下,假如通过旋转画布去绘制文字,那有些文字可是会颠倒的,这并不是我们想要的结果,那该怎么办,这时候我们只能通过数学计算老老实实的计算每个文字的起始坐标,这些坐标并没有想象中的复杂,我们可以根据中心点的位置和偏移角度(当然还需要考虑文字的宽度)算出。

难点三,绘制表针,其实文字绘制出来,那么同样可以根据中心点和偏移角度算出表针的起始坐标和结束坐标
表心就是一个实体的圆,这个就简单了。

好像还没说时分秒是怎么确定的,这当然是通过系统时间获取的了。说到这里似乎一个静态钟表已经绘制出来了,接下来让它动起来就可以了。在这我们启动一个线程,让它隔一秒钟进行一次重绘即可。

下面我直接贴一下代码把,代码是用kotlin实现(这不是重点)的

package com.example.commonui.widget

import android.annotation.SuppressLint
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.view.View
import java.util.*

/**
 * Created by zhang on 2017/12/20.
 */
class ClockView(context: Context?, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {

  companion object {
    private const val DEFAULT_WIDTH = 200 //默认宽度
  }

  private lateinit var mBlackPaint: Paint//黑色画笔
  private lateinit var mRedPaint: Paint //红色画笔
  private lateinit var mBlackPaint2: Paint//黑色画笔
  private lateinit var mTextPaint: Paint
  private var hour: Int? = null
  private var minute: Int? = null
  private var second: Int? = null
  private val textArray = arrayOf("12", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11")
  private var refreshThread: Thread? = null
  private var mHandler = @SuppressLint("HandlerLeak")
  object : Handler() {
    override fun handleMessage(msg: Message?) {
      super.handleMessage(msg)
      when (msg?.what) {
        0 -> {
          invalidate()
        }
      }

    }
  }

  init {
    initPaints()
  }

  /**
   * 初始化画笔
   */
  private fun initPaints() {
    mBlackPaint = Paint()
    with(mBlackPaint) {
      color = Color.BLACK
      strokeWidth = 5f
      isAntiAlias = true
      style = Paint.Style.STROKE
    }
    //用于画表心
    mBlackPaint2 = Paint()
    with(mBlackPaint2) {
      color = Color.BLACK
      isAntiAlias = true
      style = Paint.Style.FILL
    }
    mRedPaint = Paint()
    with(mRedPaint) {
      color = Color.RED
      strokeWidth = 5f
      isAntiAlias = true
    }

    mTextPaint = Paint()
    with(mTextPaint) {
      color = Color.BLACK
      textSize = 30f
      isAntiAlias = true
    }
  }

  override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    //获取当前时间
    getCurrentTime()

    //先画最外层的圆圈
    drawOuterCircle(canvas)

    //画刻度
    drawScale(canvas)

    //绘制文字
    drawTimeText(canvas)

    //绘制表针
    drawHand(canvas)

    //绘制表心
    drawCenter(canvas)
  }

  private fun getCurrentTime() {
    val calendar = Calendar.getInstance()
    hour = calendar.get(Calendar.HOUR)
    minute = calendar.get(Calendar.MINUTE)
    second = calendar.get(Calendar.SECOND)
  }

  private fun drawOuterCircle(canvas: Canvas?) {
    mBlackPaint.strokeWidth = 5f
    canvas?.drawCircle(measuredWidth / 2.toFloat(), measuredHeight / 2.toFloat(), (measuredWidth / 2 - 5).toFloat(), mBlackPaint)
  }

  private fun drawCenter(canvas: Canvas?) {
    canvas?.drawCircle(measuredWidth / 2.toFloat(), measuredHeight / 2.toFloat(), 20f, mBlackPaint2)
  }

  private fun drawHand(canvas: Canvas?) {
    drawSecond(canvas, mRedPaint)
    mBlackPaint.strokeWidth = 10f
    drawMinute(canvas, mBlackPaint)
    mBlackPaint.strokeWidth = 15f
    drawHour(canvas, mBlackPaint)
  }

  private fun drawTimeText(canvas: Canvas?) {
    val textR = (measuredWidth / 2 - 50).toFloat()//文字构成的圆的半径
    for (i in 0..11) {
      //绘制文字的起始坐标
      val startX = (measuredWidth / 2 + textR * Math.sin(Math.PI / 6 * i) - mTextPaint.measureText(textArray[i]) / 2).toFloat()
      val startY = (measuredHeight / 2 - textR * Math.cos(Math.PI / 6 * i) + mTextPaint.measureText(textArray[i]) / 2).toFloat()
      canvas?.drawText(textArray[i], startX, startY, mTextPaint)
    }
  }

  private fun drawScale(canvas: Canvas?) {
    var scaleLength: Float?
    canvas?.save()
    //0..59代表[0,59]
    for (i in 0..59) {
      if (i % 5 == 0) {
        //大刻度
        mBlackPaint.strokeWidth = 5f
        scaleLength = 20f
      } else {
        //小刻度
        mBlackPaint.strokeWidth = 3f
        scaleLength = 10f
      }
      canvas?.drawLine(measuredWidth / 2.toFloat(), 5f, measuredWidth / 2.toFloat(), (5 + scaleLength), mBlackPaint)
      canvas?.rotate(360 / 60.toFloat(), measuredWidth / 2.toFloat(), measuredHeight / 2.toFloat())
    }
    //恢复原来状态
    canvas?.restore()
  }

  /**
   * 绘制秒针
   */
  private fun drawSecond(canvas: Canvas?, paint: Paint?) {
    //秒针长半径 (表针会穿过表心 所以需要根据两个半径计算起始和结束半径)
    val longR = measuredWidth / 2 - 60
    val shortR = 60
    val startX = (measuredWidth / 2 - shortR * Math.sin(second!!.times(Math.PI / 30))).toFloat()
    val startY = (measuredWidth / 2 + shortR * Math.cos(second!!.times(Math.PI / 30))).toFloat()
    val endX = (measuredWidth / 2 + longR * Math.sin(second!!.times(Math.PI / 30))).toFloat()
    val endY = (measuredWidth / 2 - longR * Math.cos(second!!.times(Math.PI / 30))).toFloat()
    canvas?.drawLine(startX, startY, endX, endY, paint)
  }

  /**
   * 绘制分针
   */
  private fun drawMinute(canvas: Canvas?, paint: Paint?) {
    //半径比秒针小一点
    val longR = measuredWidth / 2 - 90
    val shortR = 50
    val startX = (measuredWidth / 2 - shortR * Math.sin(minute!!.times(Math.PI / 30))).toFloat()
    val startY = (measuredWidth / 2 + shortR * Math.cos(minute!!.times(Math.PI / 30))).toFloat()
    val endX = (measuredWidth / 2 + longR * Math.sin(minute!!.times(Math.PI / 30))).toFloat()
    val endY = (measuredWidth / 2 - longR * Math.cos(minute!!.times(Math.PI / 30))).toFloat()
    canvas?.drawLine(startX, startY, endX, endY, paint)
  }

  /**
   * 绘制时针
   */
  private fun drawHour(canvas: Canvas?, paint: Paint?) {
    //半径比秒针小一点
    val longR = measuredWidth / 2 - 120
    val shortR = 40
    val startX = (measuredWidth / 2 - shortR * Math.sin(hour!!.times(Math.PI / 6))).toFloat()
    val startY = (measuredWidth / 2 + shortR * Math.cos(hour!!.times(Math.PI / 6))).toFloat()
    val endX = (measuredWidth / 2 + longR * Math.sin(hour!!.times(Math.PI / 6))).toFloat()
    val endY = (measuredWidth / 2 - longR * Math.cos(hour!!.times(Math.PI / 6))).toFloat()
    canvas?.drawLine(startX, startY, endX, endY, paint)
  }

  /**
   * 进行测量
   */
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
    val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
    val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
    val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)
    val result = if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
      DEFAULT_WIDTH
    } else {
      Math.min(widthSpecSize, heightSpecSize)
    }

    setMeasuredDimension(result, result)
  }

  override fun onAttachedToWindow() {
    super.onAttachedToWindow()
    //启动线程 刷新界面
    refreshThread = Thread(Runnable {
      while (true) {
        try {
          Thread.sleep(1000)
          mHandler.sendEmptyMessage(0)
        } catch (e: InterruptedException) {
          break
        }
      }
    })
    refreshThread?.start()
  }

  override fun onDetachedFromWindow() {
    super.onDetachedFromWindow()
    mHandler.removeCallbacksAndMessages(null)
    //中断线程
    refreshThread?.interrupt()
  }
}

在这送上几点建议,1.尽量不要再ondraw里面创建对象,因为view可能会多次重绘,每次都创建新的对象会造成不必要的内存浪费

2.onmeasure方法会调用多次,请保证你的逻辑覆盖性,否则可能会出现没有按照你的预期得到宽高

3.线程的谨慎使用

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android打造属于自己的时间钟表

    1.概述 本文主要讲解的是如何自定义一个时间钟表,通过简单的练习可以简单学习Android当中自定义view的一些常用绘图技巧,优化android绘图操作.言归正传,首先看下我们需要实现的效果: 当我们看到这个效果的时候脑子里应该有一定的思路了,我们应该把它分解成以下几个步骤: 1.仪表盘(圆) 2.刻度线(长 中 短) 3.刻度值(1-12) 4.指针(时  分  秒) 5.移动指针,计算指针位置 现在我们已经很清楚自己的思路了,那么我们一个一个来. 第一步:1.自定义View的属性,首先在r

  • Android自定义钟表特效

    最近该忙的都忙完了,自己自定义一直是个弱项,也一直想整个钟表玩玩,网上看了一圈,学习了不少,下面自己做做自定义 首先,制作钟表第一步,肯定是画个圆吧,这是最直接的思维了! 先创建自己的自定义类,继承View ,重写构造方法,在第一个和第二个构造中初始化画笔,设置颜色等 第一个构造器类似于咱们直接New对象,第二个就是在xml文件引用时用到的 public class Watch extends View { private Paint mPaint; private Context contex

  • android自定义view实现钟表效果

    本文实例为大家分享了android view实现钟表的具体代码,供大家参考,具体内容如下 先看效果图: 自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表 当然绘制之前我们必须进行测量(重写onMeasure),根据自己的规则去测量,这暂时是将控件限制为一个正方形. 首先我们先把钟表分解,看它由哪几部分组成.如上图:钟表包括表盘(刻度)和表针还有文字构成. 分清结构之后我们再明确canvas需要画什么,表盘的构成其实就是外层一个圆,然后上面是有规律

  • Android自定义view实现太极效果实例代码

    Android自定义view实现太极效果实例代码 之前一直想要个加载的loading.却不知道用什么好,然后就想到了太极图标,最后效果是有了,不过感觉用来做loading简直丑到爆!!! 实现效果很简单,我们不要用什么贝塞尔曲线啥的,因为太极无非就是圆圆圆,只要画圆就ok了.来上代码: 因为有黑有白,所以定义2个画笔分别为黑和白. private void inital() { whitePaint = new Paint(); whitePaint.setAntiAlias(true); wh

  • Android自定义View实现打字机效果

    一.先来看看效果演示 二.实现原理: 这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放打字的音效. 具体代码如下: import java.util.Timer; import java.util.TimerTask; import android.content.Context; import android.media.MediaPlayer; import android.text.TextUtils; import android

  • Android 自定义View实现抽屉效果

    Android 自定义View实现抽屉效果 说明 这个自定义View,没有处理好多点触摸问题 View跟着手指移动,没有采用传统的scrollBy方法,而是通过不停地重新布局子View的方式,来使得子View产生滚动效果menuView.layout(menuLeft, 0, menuLeft + menuWidth, menuHeight); 相应的,由于没有使用scrollBy方法,就没有产生getScrollX值,所以不能通过Scroller的startScroll方法来完成手指离开后的平

  • Android自定义view实现圆环效果实例代码

    先上效果图,如果大家感觉不错,请参考实现代码.           重要的是如何实现自定义的view效果 (1)创建类,继承view,重写onDraw和onMesure方法 public class CirclePercentBar extends View{ private Context mContext; private int mArcColor; private int mArcWidth; private int mCenterTextColor; private int mCent

  • Android自定义View实现扫描效果

    本文实例为大家分享了Android自定义View实现扫描效果的具体代码,供大家参考,具体内容如下 演示效果如下: 实现内容: 1.控制动画是竖向或者横向 2.控制动画初始是从底部/左边开始,或者从上边/右边开始 3.控制动画的时常 4.可以自定义动画素材 具体实现: 自定义属性: <declare-styleable name="ScanView" tools:ignore="ResourceName"> <!--扫描的图片--> <a

  • Android自定义View实现时钟效果

    本文实例为大家分享了Android自定义View实现时钟效果的具体代码,供大家参考,具体内容如下 自定义时钟 初学自定义View,先画一个不太成熟的时钟(甚至只有秒针) 时钟效果 @SuppressLint("DrawAllocation") public class ClockView extends View {     private final Context mContext;     private Canvas mCanvas;// 画布     private Pain

  • Android自定义View实现风车效果

    本文实例为大家分享了Android自定义View实现风车效果的具体代码,供大家参考,具体内容如下 效果图: 画杆 public class WindmillRodView extends View {     private int mWidth;     private int mHeight;     private Paint mPaint;     public WindmillRodView(Context context) {         this(context, null);

  • Android自定义view实现输入框效果

    本文实例为大家分享了Android自定义view实现输入框的具体代码,供大家参考,具体内容如下 自定义输入框的View package com.fenghongzhang.day017; import android.content.Context; import android.content.res.TypedArray; import android.text.InputType; import android.util.AttributeSet; import android.view.

  • Android 自定义view实现TopBar效果

    本文实例为大家分享了Android自定义view实现TopBar的具体代码,供大家参考,具体内容如下 布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/t

随机推荐