首页 文章详情

质感十足的调节旋钮控件,你学会了吗?

Carson带你学习Android | 830 2021-01-12 16:48 0 0 0
UniSMS (合一短信)

我们先来看下示意图:

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

一、设计思路

有时候,用户并不需要关心控件的具体读数,在一些具有读数的音量控制控件中,我们会发现一个有意思的现象:用户会特意将数值调至整数或偶数,当用户无法调至这个数时,则会焦虑感骤增,原地螺旋爆炸。

那么,如果隐藏掉具体数值会不会更好呢?在一些老式音箱硬件上,我们会经常看到音量旋钮但不具备具体的读数,音量调至多少合适全凭入耳的感觉,感觉对了音量就对了。全程的交互中没有具体的读数概念,感觉是唯一的驱动力。

二、实现方案

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循环生成刻度形状。



「Carson每天带你学习一个Android知识点」,长按扫描关注公众号,我们明天见哦!


最后福利:学习资料赠送

  • 福利:由本人亲自撰写 & 整理的「Android学习方法资料」
  • 数量:10名
  • 参与方式:「点击文章右下角”在看“ -> 回复截图到公众号 即可,我将从中随机抽取」

    点击“在看”就能升职 & 加薪水哦!


good-icon 0
favorite-icon 0
收藏
回复数量: 0
    暂无评论~~
    Ctrl+Enter