android自定义view实现钟表效果

这篇文章主要为大家详细介绍了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.线程的谨慎使用

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

本站部分内容来源互联网,如果有图片或者内容侵犯您的权益请联系我们删除!

相关文档推荐

这篇文章主要介绍了在uiview 的tableView中点击cell进入跳转到另一个界面的实现方法,首先重写uiviewcontrol方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下
这篇文章主要介绍了iOS中 LGLAlertView 提示框的实例代码非常不错,具有参考借鉴价值,需要的朋友可以参考下
这篇文章给大家介绍iOS利用控件UIPickerView实现城市选择器的效果,选择城市这一功能相信在大家日常开发的时候经常遇见,下面就来看看详细的实现过程,有需要的可以参考借鉴。
UIScrollView是一个非常重要的控件,其可以展示比设备屏幕更大区域的内容,我们可以通过手指滑动来查看内容视图的每一部分内容,也可以通过手指捏合来对内容视图进行缩放操作,我们每天开发中都不断显式或隐式地与UIScrollView打交道,下面给大家
这篇文章主要为大家详细介绍了iOS仿热门话题热点轮播界面tableView,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
这篇文章主要介绍了IOS中UITableView滚动到指定位置的实现方法,本文介绍的非常详细,具有参考借鉴价值,需要的朋友可以参考下