一、背景
项目中的自定义均衡音效包括一组垂直的 SeekBar 以及一个 ChartView 区域。Android 中的 SeekBar 默认都是水平方向的, 因此需要自定义竖直方向的 SeekBar 。
二、步骤
1. 自定义垂直 SeekBar
-
Canvas 的旋转和平移
SeekBar默认是水平方向的, 为了让其变成竖直方向, 我们可以旋转和平移Canvas让原本水平方向的视图显示为垂直方向
Canvas坐标系 & 绘图坐标系
-
重写 onSizeChanged 方法
上面
SeekBar的绘制可以看到在旋转后的画布中仍然按照 x 轴方向为进度方向进行的绘制, 但是我们肯定是希望在 XML 中引用该控件的时候对应layout_height才是进度的方向。因此我们可以重写
onSizeChanged方法, 并交换 w 和 h的位置, 这样在绘图的时候SeekBar会自己去设置对应drawable的Bounds -
滑动事件的响应 onTouchEvent
到目前位置我们可以让
SeekBar正确的进行绘制, 但是这个SeekBar还没有办法响应竖直方向的滑动事件, 因为在SeekBar的内部是按照水平方向的移动来更新进度的。所以我们可以参考SeekBar的onTouchEvent方法来进行重写, 变成对 y 轴方向移动来更新进度。
2. ChartView 区域绘制
ChartView区域需要根据上面一组SeekBar的进度来绘制一条曲线, ChartView 对 SeekBar 的感知可以通过自定义一个 Listener 来实现, ChartView实现 Listener 接口, 每个 SeekBar 都将 ChartView 注册, 在进度发送变化的时候通知 Listener。曲线的绘制采用贝塞尔曲线, 在每个进度条的左右两边计算起点和终点, 然后将当前进度条的值作为控制点。
三、踩坑
1. 使用 setProgress 方法重置时进度条底部缺失一块
-
setProgress
重置的时候调用
setProgress方法设置进度, 首先在ProgressBar中更新了progressDrawable的位置, 然后调用了一个空方法onVisualProgressChanged, 该方法在ABsSeekBar中进行了重写, 主要调用了setThumbPos方法来设置thumbDrawable的Bounds, 传递的第一个参数为getWidth(), 这其实是SeekBar水平的情况下Thumb位置的更新方式。 -
onDraw
SeekBar进行绘制可以分成两步, 第一步是绘制进度条(ProgressDrawable), 第二步是绘制游标(ThumbDrawable)。在源码中分别对应drawTrack和drawThumb两个方法,drawTrack实际实现是在ProgressBar中, 不过AbsSeekBar对它进行了重写在绘制之间截掉了Thumb所处的位置, 之后再调用ProgressBar的drawTrack进行绘制, 由于Thumb的位置其实是错误的, 因此就会导致在底部被截掉一段的情况。 -
解决方法
每次调用
setProgress更新进度的时候再调用一下onSizeChanged方法, 该方法内也会调用setThumbPos来更新thumbDrawable的Bounds, 传递的第一个参数为onSizeChanged的 w, 但是我们重写了onSizeChanged方法, 所以w实际对应的是View的高度, 因此位置就能正确的更新。