无极旋钮控件,隔壁产品都馋哭了

1,575 阅读3分钟
阅读完本文约需6分钟。
废不说,看图

质感十足,适合用在一些需要对物联网智能设备进行控制的场景,比如调节某个智能音箱的音量
稍加改造,也可用在一些类似交互的控件上

一、设计思路

有时候,用户并不需要关心控件的具体读数,在一些具有读数的音量控制控件中,我们会发现一个有意思的现象:用户会特意将数值调至整数或偶数,当用户无法调至这个数时,则会焦虑感骤增,原地螺旋爆炸。如同吃完重庆火锅后往菊花里塞了颗薄荷糖——微辣中带一丝清凉。
那么,如果隐藏掉具体数值会不会更好呢?在一些老式音箱硬件上,我们会经常看到音量旋钮但不具备具体的读数,音量调至多少合适全凭入耳的感觉,感觉对了音量就对了。全程的交互中没有具体的读数概念,感觉是唯一的驱动力。

二、实现方案

2.1 UI拆解

老套路,首先分析形状,不难发现,由旋钮、旋钮上的指示器、外围刻度构成

2.2 UI绘制

UI整体绘制难度非常简单,主要在ondraw中

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    setLayerType(LAYER_TYPE_SOFTWARE, null)
    canvas?.let {
        //刻度
        drawScale(it)
        //旋钮&阴影
        drawShadow(it)
        //指示器
        drawIndicator(it)
    }
}

2.2.1 绘制旋钮

绘制圆形的同时绘制阴影,增加质感

代码如下

private fun drawShadow(canvas: Canvas) {
    paint.color = Color.WHITE
    paint.setShadowLayer(shadowSize, 0F, 15F, Color.GRAY)
    canvas.drawCircle(centerX, centerY, radius, paint)
    paint.clearShadowLayer()
}

2.2.2 绘制指示器

代码如下,需要注意的是,指示器带有角度属性,需要旋转画布

private fun drawIndicator(canvas: Canvas) {
    canvas.save()
    canvas.rotate(circularOpUtils.curDegree, centerX, centerY)
    paint.color = Color.RED
    canvas.drawRect(RectF(centerX + radius / 4, centerY - 4, centerX + radius * 3 / 4, centerY + 4), paint)
    canvas.restore()
}

2.2.3 绘制刻度

代码如下:

private fun drawScale(canvas: Canvas) {
    Log.i(TAG, "curDegree==>" + circularOpUtils.curDegree)
    canvas.save()
    paint.color = Color.GRAY
    var scaleCount = 360 / scaleSpace.toInt()
    for (i in 0 until scaleCount) {
        //绘制当前指示
        if ((i * scaleSpace <= circularOpUtils.curDegree) && ((i + 1) * scaleSpace > circularOpUtils.curDegree)) {
            Log.i(TAG, "i*scaleSpace==>" + i * scaleSpace)
            paint.color = curSelScaleColor
            canvas.drawRect(width - scaleWidth, centerY - 4F, width - scaleWidth + curSelScaleWith, centerY + 4F, paint)
            paint.color = Color.GRAY
        } else {
            canvas.drawRect(width - scaleWidth, centerY - 4F, width.toFloat(), centerY + 4F, paint)
        }
        canvas.rotate(scaleSpace, centerX, centerY)
    }
    canvas.restore()
}

2.3 交互实现

按交互抽象出计算旋转角度的工具类CircularOpUtils,类似圆盘旋转的控件都可通用
2.3.1 旋转角度计算
思路是这样的,在手指移动中如何计算移动点和起始按压点的角度呢?可以采用几何公式,利用反tan或反cos;也可以采用两点分别与x轴的夹角的差进行计算。这里采用后者

/**
* 计算坐标点与x轴的夹角
*/
fun calculateAngle(x: Float, y: Float): Float {
    val distance = sqrt(((x - centerX) * (x - centerX) + (y - centerY) * (y - centerY)))
    if (distance == 0F) {
        return 0F
    }
    var degree = acos((x - centerX) / distance) * 180 / PI.toFloat()
    if (y < centerY) {
        degree = 360 - degree
    }
    return degree
}
/**
* 计算两点的夹角
*/
fun calculateAngle(x1: Float, y1: Float, x2: Float, y2: Float): Float {
    val angle1 = calculateAngle(x1, y1)
    val angle2 = calculateAngle(x2, y2)
    return angle2 - angle1
}

2.3.2 刻度动画
主要是当前选中刻度的长短变化

private fun shrinkScaleAnim() {
    shrinkScaleAnim?.cancel()
    if (curSelScaleWith > 10F) {
        shrinkScaleAnim = ValueAnimator.ofFloat(curSelScaleWith, 10F)
        with(shrinkScaleAnim!!) {
            duration = 300L
            addUpdateListener {
                curSelScaleWith = it.animatedValue as Float
                postInvalidate()
            }
            start()
        }
    }
}

2.3.3 一点细节
可以观察到,用户手指按压和抬起时,旋钮的阴影变化

private fun startUpShadowAnim() {
    shadowAnim?.cancel()
    shadowAnim = ValueAnimator.ofFloat(shadowSize, 30F)
    with(shadowAnim!!) {
        duration = 300L
        addUpdateListener {
            shadowSize = animatedValue as Float
            postInvalidate()
        }
        start()
    }
}
private fun startDownShadowAnim() {
    shadowAnim?.cancel()
    shadowAnim = ValueAnimator.ofFloat(shadowSize, 20F)
    with(shadowAnim!!) {
        duration = 300L
        addUpdateListener {
            shadowSize = animatedValue as Float
            postInvalidate()
        }
        start()
    }
}

2.3.4 优化思路
这个控件涉及了刻度绘制,当没有动画在运行时,可以将刻度的形状使用path保存下来,用户交互时,旋转path即可,而不需要每次在ondraw中for循环生成刻度形状。

三、后记

如何减少用户在交互时的焦虑感?这是设计上可以探索的方向。

但是用户需要减少焦虑吗?现实中的应用都巴不得促进用户越来越焦虑。
看看吧
可以无限往下刷的信息列表;
可以无限刷的短视频;
可以无限下翻的回答、帖子;
无限次下拉刷新;
文字社区强行添加视频流;
腹泻式不停地兴趣推荐......
什么时候才是个头啊?

那种调节音箱音量旋钮时感觉对了就好了的体验不复存在了。

这样真的好吗?
我说,不好。
但我算个屁。

啊,既然看到了这里,是时候亮出我的个人微信和公众号了......才怪!