Flutter实现拼音四线三格和字母对齐——我的第一个Flutter包!

1 阅读5分钟

前言

在过去的一段时间里,我所在实习的公司正在考虑采用Flutter框架,因为现在有鸿蒙之后三端在人力上还是消耗很大。

于是我就在调研Flutter在鸿蒙上迁移的可能性,我选了公司的一个相对较小还比较全面的项目,进行部分核心功能落地,这其中就孵化了今天要和大家分享的东西——四线三格和拼音绘制,让我们的拼音可以正确的落到四线三格上。

如果你正在寻找这个功能,直接在底部,我已经将这个库发布在了pub.dev

效果

效果.png

这里我展示了4种不同的情况,基本上满足了当前的需求:

  1. 支持文本、字体设置
  2. 支持调整线条粗细、颜色
  3. 支持对齐及传入其他控件
  4. 支持Warp自动换行,自动填充后续的线条

实现思路

在安卓原生的实现思路很朴素,那就是纯靠经验去设置四条线的间距,让字母底部落到第三条线上,然后调整其它几条线的的间距。 但这样的问题很大,只要你修改字体大小,就必须去重新调整线条,虽然不是不能用,但灵活性基本上拉完了。

所以我们需要换一种办法,即使是修改字体大小,也可以让拼音准确的落到四线三格上。

四线三格

四线三格与文本.png

让我们看看这个例子,我们可以清楚的看到,现在我们使用了四条线,将区域划分为了三块,分别是上、中、下格。

格子高度

我们要让字母a的顶部和顶部正好在2,3条线上,那需要重点关注的就只有确定第2、3条线的位置,也就是每个格子的高度。

现在我们需要知道字体大小和这个格子高度的关系,这样才能确保字体大小调整后线条位置始终可以在正确的位置。

这里我在网上看了看,东西很杂,最后问ai确认了一个大致的比例,那就是0.5,这一格高度大约是0.5,这样我们就可以绘制出大致的位置了。

格子绘制

那总的高度就是1.5,因为有3格。

这里大家可以用Stack+Container,也可以用CustomPaint,下面我们使用CustomPaint进行绘制,因为随后推荐给我的就是这个。

/// 四线三格绘制器
class _WritingGuidelinesPainter extends CustomPainter {
  final Color color;
  final double gridHeight;
  final double lineWidth;

  const _WritingGuidelinesPainter({
    required this.color,
    required this.gridHeight,
    required this.lineWidth,
  });
}

这里我们自定义了一个CustomPainter,主要是3个参数,颜色,格子总高度,线条宽度。

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..strokeWidth = lineWidth
      ..style = PaintingStyle.stroke;

    final spacing = gridHeight / 3;

    for (var i = 0; i < 4; i++) {
      final y = i * spacing;
      canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
    }
  }

这里我们创建一个画笔,设置好样式,然后我们区分出每个线条的间距spacing,每隔这个距离就画一笔。

然后我们使用它:

SizedBox(
  width: 300,
  child: CustomPaint(
    painter: _WritingGuidelinesPainter(
      color: Colors.purpleAccent,
      gridHeight: 60 * 1.5,
      lineWidth: 3,
    ),
  ),
)

这块我们假设文本字体是60,那总高度就是60 * 1.5,然后我们设置一下线宽和颜色。

四线三格.png

这样我们就画出四条线了,你还可以考虑让2,3线是虚线,这样可能更符合四线三格。

控制文本基线

现在我们可以加一个childCustomPaint,就是直接堆叠上去,相当于我们在画布上面。

SizedBox(
  width: 300,
  child: CustomPaint(
    painter: _WritingGuidelinesPainter(
      color: Colors.purpleAccent,
      gridHeight: 60 * 1.5,
      lineWidth: 3,
    ),
    child: Text("a",style: TextStyle(
      fontSize: 60
    ),),
  ),
)

四线三格带未对齐文本.png

但是大家会发现问题,这天塌了啊,怎么按照比例写完还是没对齐?

现在让我们请出我们的基线组件:

SizedBox(
  width: 300,
  child: CustomPaint(
    painter: _WritingGuidelinesPainter(
      color: Colors.purpleAccent,
      gridHeight: 60 * 1.5,
      lineWidth: 3,
    ),
    child: Baseline(
      baseline: 60 * 1.5 * 2/3,
      baselineType: TextBaseline.alphabetic,
      child: Text("a", style: TextStyle(fontSize: 60)),
    ),
  ),
)

这里我们加入了一个Baseline,然后baseline位置的60 * 1.5 * 2/3是怎么来的? 60 * 1.5我们知道了是3个格子的总高度,那2/3就是到第二个格子的底部,也就是第三条线的位置。

四线三格带对齐文本.png

大功告成!恭喜你已经成功绘制了一个四线三格,并且还很自然的对齐了文本,当然这个我们选的1.5精确可能还差一点,大家可以自己再调整。

快速开始

上面我们已经讲解了实现原理,但大家不必从头开始,我已经封装好了一个组件库,方便大家使用: writing_guidelines

我其实在安卓的时候就在考虑找一个库,包括在Flutter时也是,但是都没能找到,所以我打算将这个库开源出来,这样下一个人就有工具可用了!我觉得这就是我开源这个库的意义所在。

这也是我第一次发布Flutter包,充满激情,大家可以直接使用这个命令一键安装,有问题也可以指出:

flutter pub add writing_guidelines

简单用法

我为大家封装了一个简单的构造函数,如果你需要最基础的样式,那么就这样写:

WritingGuidelines.text(
  "huān yíng".replaceAWithAlpha(),
  textStyle: TextStyle(fontSize: 60),
  fontSize: 60,
  lineWidth: 4,
),

image.png

这样我们就得到了一个最简单好用的拼音展示了,这里我们调用了一个扩展函数——replaceAWithAlpha,他会把a替换为α,看上去更像是拼音a的手写体。

更灵活的组合

如果你除了显示文本,还希望进行修饰,可以使用direct工厂构造函数。

WritingGuidelines.direct(
  fontSize: 60,
  child: Row(
    mainAxisAlignment: .center,
    crossAxisAlignment: .center,
    children: [
      Text("běi jīng", style: TextStyle(fontSize: 60)),
      Container(
        decoration: BoxDecoration(
          shape: .circle,
          color: Colors.blue,
        ),
        width: 60,
        height: 60,
      ),
    ],
  ),
)

image.png

Warp自动换行

有时候我们需要展示一个列表,进行自动换行,但如果第二行第一个文本很长,就会导致第一行最后空出来了很大的位置,四线三格中断了,不像是作业本那样一行是完整的。

于是我还支持了Warp布局:

child: WritingGuidelinesWrap(
  fontSize: 30,
  runSpacing: 5,
  lineColor: Colors.pink,
  children: _pinyinTexts
      .map((text) => Text(text, style: TextStyle(fontSize: 30)))
      .toList(),
),

image.png

现在,我们可以清晰的看到,四线三格在这种情况下仍然完整连续。

更多的用法可以参考 writing_guidelines 组件库。

文末

感谢大家看到这里,如果有错误请大家指出。