系列文章
造轮子:滚轮选择器实现及原理解析(一)
造轮子:滚轮选择器实现及原理解析(二)
造轮子:滚轮选择器实现及原理解析(三)
造轮子:滚轮选择器实现及原理解析(源码)
实现效果
上方非循环滚动,下方循环滚动
滚动下面的选择器使上方选择器联动
需求拆解
滚轮选择器的使用场景最常见于时间选择,手机上操作出生年月时经常能看到年月日选择器。
其最典型特征:
- 数据在同一列展示
- 中间部分突出显示
- 有3D环绕感
- 支持循环滚动
- 拥有惯性滚动与就近吸附动画
抛开动画和展示效果不谈,首要任务在于如何把内容排列在屏幕上。
模拟顺序排列
视觉上典型的LinearLayout
布局,从上到下按顺序排列,甚至滚动都是类似的,不过我们这里不使用LinearLayout
,当需要循环时使用LinearLayout
并不方便
// onDraw()
for (int i = 0; i < size; i++) {
drawItem(canvas, i);
}
// drawItem()
// 测量文字,绘制时居中显示
float textWidth = paint.measureText(text);
// 计算偏移量,在每行固定高度时,偏移量很好计算
float totalOffset = i* itemHeight;
canvas.save();
canvas.translate(0, -totalOffset);
// 此处用于适配文字baseline,否则会导致文字无法绘制在绝对居中位置
Paint.FontMetrics metrics = paint.getFontMetrics();
canvas.drawText(text, width / 2f - textWidth / 2f, - (metrics.top + metrics.bottom) / 2f, paint);
canvas.restore();
发现问题
- 在滚动时滚动距离会实时变化,没办法仅仅使用
i*itemHeight
计算偏移 - 滚动默认位置应当从正中间开始的,而非顶部
- 高度就那么高,我的数据过多时也看不到
改造
- 使用临时变量
curY
模拟当前滚动距离,使用该变量计算 - 因为是从中间开始滚动,我们绘制方式调整为从中间发散绘制(对于3D视图来说,中间处于缩放点,从中间开始计算更方便,后续会用到)
- 限制可见个数与绘制个数,全部绘制浪费性能不说实际也看不到
1.根据当前滚动距离得到中间位置的下标
protected int getCenterShowPosition(float y) {
//这里进行y矫正,防止小于0或超出float上限,便于后续计算
float newY = adjustingY(y);
return (int) (newY / itemHeight);
}
现在我们将模型图转换:
因为我们绘制文本时,总是居中绘制,所以理论上我们只关心绘制点的中心线段
在哪,后续绘制时根据itemHeight
进行平移矫正一下,后续我们只关心线条的位置即可
2. 改变整体绘制逻辑
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
float y = curY;
// 获取中心位置item的下标
int centerPosition = getCenterShowPosition(y);
// item不可能刚好在中线,计算出它的偏移距离
float offsetY = adjustingY(y) - itemHeight * centerPosition;
// 限制绘制个数,为了保证上下对称,当向上滚动一段距离时,应当在下方多补充一个item绘制
int max = centerPosition + showCount;
// 处于正中心时使两侧显示相同个数item,非中心时下方增加1个
if (offsetY > 0f) {
max += 1;
}
// 只遍历可显示出来的部分
for (int i = centerPosition - showCount + 1; i < max; i++) {
drawItem(canvas, i, centerPosition, offsetY);
}
}
3. 改变item绘制逻辑
// 计算和中心item的差距
int count = centerPosition - position;
float totalOffset = offsetY + count * itemHeight;
canvas.save();
canvas.translate(0, -totalOffset);
Paint.FontMetrics metrics = paint.getFontMetrics();
// 绘制在中心点的位置
canvas.drawText(text, width / 2f - textWidth / 2f, height / 2f - (metrics.top + metrics.bottom) / 2f, paint);
canvas.restore();
至此,简单的滚轮选择器的基础绘制已完成,调整curY
可使其位置发生改变
本文由 bt 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。