自定义均衡器UI实现

1,124 阅读3分钟

一、背景

项目中的自定义均衡音效包括一组垂直的 SeekBar 以及一个 ChartView 区域。Android 中的 SeekBar 默认都是水平方向的, 因此需要自定义竖直方向的 SeekBar

二、步骤

1. 自定义垂直 SeekBar

  • Canvas 的旋转和平移

    SeekBar 默认是水平方向的, 为了让其变成竖直方向, 我们可以旋转和平移Canvas让原本水平方向的视图显示为垂直方向

    Canvas 旋转平移.png

    Canvas坐标系 & 绘图坐标系

  • 重写 onSizeChanged 方法

    上面 SeekBar 的绘制可以看到在旋转后的画布中仍然按照 x 轴方向为进度方向进行的绘制, 但是我们肯定是希望在 XML 中引用该控件的时候对应 layout_height才是进度的方向。

    因此我们可以重写 onSizeChanged 方法, 并交换 w 和 h的位置, 这样在绘图的时候 SeekBar 会自己去设置对应 drawableBounds

  • 滑动事件的响应 onTouchEvent

    到目前位置我们可以让 SeekBar 正确的进行绘制, 但是这个 SeekBar 还没有办法响应竖直方向的滑动事件, 因为在 SeekBar 的内部是按照水平方向的移动来更新进度的。所以我们可以参考 SeekBaronTouchEvent 方法来进行重写, 变成对 y 轴方向移动来更新进度。

2. ChartView 区域绘制

ChartView区域需要根据上面一组SeekBar的进度来绘制一条曲线, ChartViewSeekBar 的感知可以通过自定义一个 Listener 来实现, ChartView实现 Listener 接口, 每个 SeekBar 都将 ChartView 注册, 在进度发送变化的时候通知 Listener。曲线的绘制采用贝塞尔曲线, 在每个进度条的左右两边计算起点和终点, 然后将当前进度条的值作为控制点。

三、踩坑

1. 使用 setProgress 方法重置时进度条底部缺失一块

  • setProgress

    重置的时候调用 setProgress 方法设置进度, 首先在 ProgressBar 中更新了 progressDrawable 的位置, 然后调用了一个空方法 onVisualProgressChanged, 该方法在 ABsSeekBar 中进行了重写, 主要调用了 setThumbPos 方法来设置 thumbDrawableBounds, 传递的第一个参数为 getWidth(), 这其实是 SeekBar 水平的情况下 Thumb 位置的更新方式。

  • onDraw

    SeekBar 进行绘制可以分成两步, 第一步是绘制进度条(ProgressDrawable), 第二步是绘制游标(ThumbDrawable)。在源码中分别对应 drawTrackdrawThumb 两个方法, drawTrack 实际实现是在 ProgressBar 中, 不过 AbsSeekBar 对它进行了重写在绘制之间截掉了 Thumb 所处的位置, 之后再调用 ProgressBardrawTrack进行绘制, 由于 Thumb 的位置其实是错误的, 因此就会导致在底部被截掉一段的情况。

  • 解决方法

    每次调用 setProgress 更新进度的时候再调用一下 onSizeChanged 方法, 该方法内也会调用 setThumbPos 来更新 thumbDrawableBounds, 传递的第一个参数为 onSizeChanged 的 w, 但是我们重写了 onSizeChanged 方法, 所以w实际对应的是View的高度, 因此位置就能正确的更新。