You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android自定义CustomWheelView选中结果与显示不符问题求助

转盘控件显示与输出不匹配问题修复

问题描述

开发的CustomWheelView转盘停止时,指针指向的选项与输出结果不一致,例如指针停在Italian选项时,输出却显示“你选择了Indian”。

核心原因

代码同时使用了自定义angle变量控制绘制和View的rotation动画,导致实际显示的旋转角度与代码中用于计算选中项的angle变量不一致;再加上选中索引的计算逻辑存在角度偏移错误,最终引发匹配问题。

修复方案

方案一:使用自定义角度变量控制旋转(推荐)

移除View的rotation动画,改用ValueAnimator更新自定义angle变量,确保绘制和选中项计算使用同一角度源:

  1. 修改spinWheel方法(需导入android.animation.Animator和android.animation.ValueAnimator):
fun spinWheel() {
    if (!isSpinning) {
        isSpinning = true
        val totalSpinAngle = (360 * 5 + (0..360).random()).toFloat()
        val startAngle = angle
        val endAngle = startAngle + totalSpinAngle

        ValueAnimator.ofFloat(startAngle, endAngle).apply {
            duration = 3000
            addUpdateListener { animation ->
                angle = animation.animatedValue as Float
                invalidate()
            }
            addListener(object : Animator.AnimatorListener {
                override fun onAnimationStart(animation: Animator) {}
                override fun onAnimationEnd(animation: Animator) {
                    isSpinning = false
                    // 执行弹跳效果
                    ValueAnimator.ofFloat(angle, angle + 5f).apply {
                        duration = 100
                        addUpdateListener { angle = it.animatedValue as Float; invalidate() }
                        addListener(object : Animator.AnimatorListener {
                            override fun onAnimationStart(animation: Animator) {}
                            override fun onAnimationEnd(animation: Animator) {
                                ValueAnimator.ofFloat(angle, angle - 5f).apply {
                                    duration = 100
                                    addUpdateListener { angle = it.animatedValue as Float; invalidate() }
                                    addListener(object : Animator.AnimatorListener {
                                        override fun onAnimationStart(animation: Animator) {}
                                        override fun onAnimationEnd(animation: Animator) {
                                            angle = (angle - 5f) % 360
                                            calculateAndNotifySelectedItem()
                                        }
                                        override fun onAnimationCancel(animation: Animator) {}
                                        override fun onAnimationRepeat(animation: Animator) {}
                                    })
                                    start()
                                }
                            }
                            override fun onAnimationCancel(animation: Animator) {}
                            override fun onAnimationRepeat(animation: Animator) {}
                        })
                        start()
                    }
                }
                override fun onAnimationCancel(animation: Animator) {}
                override fun onAnimationRepeat(animation: Animator) {}
            })
            start()
        }
    }
}

private fun calculateAndNotifySelectedItem() {
    val sweepAngle = 360f / segments.size
    val normalizedAngle = (angle % 360 + 360) % 360 // 确保角度为正
    val relativeTopAngle = (360 - normalizedAngle) % 360 // 计算指针(顶部)相对于转盘的角度
    val selectedIndex = (relativeTopAngle / sweepAngle).toInt() % segments.size
    onWheelStoppedListener?.invoke(segments[selectedIndex])
}
  1. 保留原onDraw方法不变,它已基于angle变量正确绘制转盘。

方案二:使用View的rotation控制旋转

放弃自定义angle变量,直接用View的rotation属性获取当前角度,同步绘制和计算逻辑:

  1. 修改onDraw方法:
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    val width = width.toFloat()
    val height = height.toFloat()
    val radius = min(width, height) / 2
    rectF.set(width / 2 - radius, height / 2 - radius, width / 2 + radius, height / 2 + radius)

    val currentAngle = (rotation % 360 + 360) % 360 // 获取当前View的旋转角度并归一化
    val sweepAngle = 360f / segments.size

    for (i in segments.indices) {
        paint.shader = LinearGradient(
            rectF.centerX(), rectF.top,
            rectF.centerX(), rectF.bottom,
            gradientColors[i % gradientColors.size],
            gradientColors[(i + 1) % gradientColors.size],
            Shader.TileMode.CLAMP
        )
        canvas.drawArc(rectF, currentAngle + i * sweepAngle, sweepAngle, true, paint)

        val textAngle = Math.toRadians((currentAngle + i * sweepAngle + sweepAngle / 2).toDouble())
        val x = (width / 2 + radius * 0.65 * Math.cos(textAngle)).toFloat()
        val y = (height / 2 + radius * 0.65 * Math.sin(textAngle)).toFloat()
        canvas.drawText(segments[i], x, y, textPaint)
    }
}
  1. 修改spinWheel方法中的选中项计算逻辑:
fun spinWheel() {
    if (!isSpinning) {
        isSpinning = true
        val randomAngle = (360 * 5 + (0..360).random()).toFloat()

        animate()
            .rotation(randomAngle)
            .setDuration(3000)
            .withEndAction {
                animate().rotationBy(5f).setDuration(100).withEndAction {
                    animate().rotationBy(-5f).setDuration(100).withEndAction {
                        isSpinning = false
                        invalidate()

                        val sweepAngle = 360f / segments.size
                        val currentAngle = (rotation % 360 + 360) % 360
                        val relativeTopAngle = (360 - currentAngle) % 360
                        val selectedIndex = (relativeTopAngle / sweepAngle).toInt() % segments.size

                        onWheelStoppedListener?.invoke(segments[selectedIndex])
                    }.start()
                }.start()
            }
            .start()
    }
}

说明

两种方案都能解决显示与输出不匹配的问题,方案一更灵活,便于后续扩展转盘的自定义逻辑;方案二更贴合View的原生动画机制。

内容的提问来源于stack exchange,提问作者Islam Farrag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 00:54:50