IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    造轮子:滚轮选择器实现及原理解析(一)

    bt发表于 2023-07-21 13:21:00
    love 0

    系列文章
    造轮子:滚轮选择器实现及原理解析(一)
    造轮子:滚轮选择器实现及原理解析(二)
    造轮子:滚轮选择器实现及原理解析(三)
    造轮子:滚轮选择器实现及原理解析(源码)

    实现效果

    上方非循环滚动,下方循环滚动
    滚动下面的选择器使上方选择器联动
    飞书20230720-154126.gif

    需求拆解

    滚轮选择器的使用场景最常见于时间选择,手机上操作出生年月时经常能看到年月日选择器。
    其最典型特征:

    1. 数据在同一列展示
    2. 中间部分突出显示
    3. 有3D环绕感
    4. 支持循环滚动
    5. 拥有惯性滚动与就近吸附动画

    抛开动画和展示效果不谈,首要任务在于如何把内容排列在屏幕上。

    模拟顺序排列

    2023-07-21T05:41:18.png
    视觉上典型的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();

    2023-07-21T05:49:09.png

    发现问题

    1. 在滚动时滚动距离会实时变化,没办法仅仅使用i*itemHeight计算偏移
    2. 滚动默认位置应当从正中间开始的,而非顶部
    3. 高度就那么高,我的数据过多时也看不到

    改造

    1. 使用临时变量curY模拟当前滚动距离,使用该变量计算
    2. 因为是从中间开始滚动,我们绘制方式调整为从中间发散绘制(对于3D视图来说,中间处于缩放点,从中间开始计算更方便,后续会用到)
    3. 限制可见个数与绘制个数,全部绘制浪费性能不说实际也看不到

    1.根据当前滚动距离得到中间位置的下标

    protected int getCenterShowPosition(float y) {
        //这里进行y矫正,防止小于0或超出float上限,便于后续计算
        float newY = adjustingY(y);
        return (int) (newY / itemHeight);
    }

    现在我们将模型图转换:
    因为我们绘制文本时,总是居中绘制,所以理论上我们只关心绘制点的中心线段在哪,后续绘制时根据itemHeight进行平移矫正一下,后续我们只关心线条的位置即可
    2023-07-21T06:09:08.png

    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可使其位置发生改变
    2023-07-21T06:25:13.png



沪ICP备19023445号-2号
友情链接