返回

小米时间控件的精简版探索之旅

Android

自定义 Kotlin 时间控件:绘制精简版小米时钟

引言

在智能手机的世界里,时间控件是一个必不可少的元素。从简单的时钟到功能丰富的日期选择器,它们帮助我们跟踪时间并与数字世界互动。小米在其设备中引入了令人印象深刻的时间控件,融合了流畅的动画和直观的交互。

本教程将指导您使用 Kotlin 构建一个精简版的小米时间控件。我们将分解设计、布局和代码实现,以便您深入了解自定义控件的强大功能。

设计与布局

小米的时间控件采用圆形设计,小时和分钟指针沿圆弧移动。它还具有一个中央按钮,用于在时钟和日期模式之间切换。

对于我们的简化版本,我们将使用类似的设计,同时保持灵活性以进行自定义。

// 定义自定义时间控件的布局
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)

    // 计算控件中心点
    val centerX = (right - left) / 2
    val centerY = (bottom - top) / 2

    // 设置指针的初始位置
    val hourHandRotation = calculateHourHandRotation(hour)
    val minuteHandRotation = calculateMinuteHandRotation(minute)
    hourHand.rotation = hourHandRotation
    minuteHand.rotation = minuteHandRotation

    // 设置中央按钮的位置
    centralButton.x = (centerX - centralButton.width / 2).toFloat()
    centralButton.y = (centerY - centralButton.height / 2).toFloat()
}

实现动画

小米时间控件的动画是其魅力的关键所在。指针沿圆弧平滑移动,创建一种身临其境的体验。

// 计算指针旋转角度
private fun calculateHourHandRotation(hour: Int): Float {
    return (hour * 360) / 12
}

private fun calculateMinuteHandRotation(minute: Int): Float {
    return (minute * 360) / 60
}

// 通过属性动画实现指针旋转
private fun animatePointerRotation(pointer: View, startAngle: Float, endAngle: Float) {
    val animator = ObjectAnimator.ofFloat(pointer, "rotation", startAngle, endAngle)
    animator.duration = 300
    animator.interpolator = AccelerateDecelerateInterpolator()
    animator.start()
}

交互

小米的时间控件具有直观的交互,包括点击中央按钮在时钟和日期模式之间切换。

// 中央按钮的点击监听器
private fun setCentralButtonClickListener() {
    centralButton.setOnClickListener {
        // 根据当前模式切换模式
        if (isClockMode) {
            isClockMode = false
        } else {
            isClockMode = true
        }

        // 更新控件的外观
        invalidate()
    }
}

代码示例

以下是完整的 Kotlin 代码示例:

// 自定义时间控件类
class CustomTimeView(context: Context, attrs: AttributeSet?) : View(context, attrs) {

    // 指针视图
    private val hourHand: View
    private val minuteHand: View

    // 中央按钮视图
    private val centralButton: View

    // 当前模式(时钟或日期)
    private var isClockMode = true

    // 初始化
    init {
        val inflater = LayoutInflater.from(context)
        val view = inflater.inflate(R.layout.custom_time_view, this, true)

        hourHand = view.findViewById(R.id.hour_hand)
        minuteHand = view.findViewById(R.id.minute_hand)
        centralButton = view.findViewById(R.id.central_button)

        // 设置中央按钮的点击监听器
        setCentralButtonClickListener()
    }

    // 计算指针旋转角度
    private fun calculateHourHandRotation(hour: Int): Float {
        return (hour * 360) / 12
    }

    private fun calculateMinuteHandRotation(minute: Int): Float {
        return (minute * 360) / 60
    }

    // 通过属性动画实现指针旋转
    private fun animatePointerRotation(pointer: View, startAngle: Float, endAngle: Float) {
        val animator = ObjectAnimator.ofFloat(pointer, "rotation", startAngle, endAngle)
        animator.duration = 300
        animator.interpolator = AccelerateDecelerateInterpolator()
        animator.start()
    }

    // 中央按钮的点击监听器
    private fun setCentralButtonClickListener() {
        centralButton.setOnClickListener {
            // 根据当前模式切换模式
            if (isClockMode) {
                isClockMode = false
            } else {
                isClockMode = true
            }

            // 更新控件的外观
            invalidate()
        }
    }

    // 绘制
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        // 根据模式绘制时钟或日期
        if (isClockMode) {
            drawClock(canvas)
        } else {
            drawDate(canvas)
        }
    }

    // 绘制时钟
    private fun drawClock(canvas: Canvas?) {
        // 计算指针当前旋转角度
        val hour = Calendar.getInstance().get(Calendar.HOUR)
        val minute = Calendar.getInstance().get(Calendar.MINUTE)
        val hourHandRotation = calculateHourHandRotation(hour)
        val minuteHandRotation = calculateMinuteHandRotation(minute)

        // 设置指针旋转
        hourHand.rotation = hourHandRotation
        minuteHand.rotation = minuteHandRotation

        // 绘制圆形时钟面
        canvas?.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), (width / 2).toFloat(), Paint().apply { color = Color.BLACK })

        // 绘制小时和分钟刻度
        for (i in 0 until 12) {
            val hourAngle = (i * 360) / 12
            val minuteAngle = (i * 6) * 6
            val radius = (width / 2 * 0.9).toFloat()
            val smallRadius = (width / 2 * 0.8).toFloat()

            // 绘制小时刻度
            val hourX = (centerX + Math.cos(Math.toRadians(hourAngle.toDouble())) * radius).toFloat()
            val hourY = (centerY + Math.sin(Math.toRadians(hourAngle.toDouble())) * radius).toFloat()
            canvas?.drawCircle(hourX, hourY, 5f, Paint().apply { color = Color.BLACK })

            // 绘制分钟刻度
            val minuteX = (centerX + Math.cos(Math.toRadians(minuteAngle.toDouble())) * smallRadius).toFloat()
            val minuteY = (centerY + Math.sin(Math.toRadians(minuteAngle.toDouble())) * smallRadius).toFloat()
            canvas?.drawCircle(minuteX, minuteY, 2f, Paint().apply { color = Color.BLACK })
        }
    }

    // 绘制日期
    private fun drawDate(canvas: Canvas?) {
        // 获取当前日期
        val calendar = Calendar.getInstance()
        val day = calendar.get(Calendar.DAY_OF_MONTH)
        val month = calendar.get(Calendar.MONTH)
        val year = calendar.get(Calendar.YEAR)

        // 设置文本大小和颜色
        val paint = Paint().apply {
            textSize = 50f
            color = Color.BLACK
        }

        // 绘制日期文本
        canvas?.drawText("$day/$month/$year", (width / 2 - paint.measureText("$day/$month/$year") / 2).toFloat(), (height / 2).toFloat(), paint)
    }
}

结论

通过遵循本教程,您已经成功构建了一个自定义的小米时间控件,具有类似的动画效果和直观交互。通过了解 Kotlin 的强大功能和自定义控件的原理,您可以创建各种创新且引人注目的 Android UI 元素。