前言
在过去的一段时间里,我所在实习的公司正在考虑采用Flutter框架,因为现在有鸿蒙之后三端在人力上还是消耗很大。
于是我就在调研Flutter在鸿蒙上迁移的可能性,我选了公司的一个相对较小还比较全面的项目,进行部分核心功能落地,这其中就孵化了今天要和大家分享的东西——四线三格和拼音绘制,让我们的拼音可以正确的落到四线三格上。
如果你正在寻找这个功能,直接在底部,我已经将这个库发布在了pub.dev。
效果
这里我展示了4种不同的情况,基本上满足了当前的需求:
- 支持文本、字体设置
- 支持调整线条粗细、颜色
- 支持对齐及传入其他控件
- 支持Warp自动换行,自动填充后续的线条
实现思路
在安卓原生的实现思路很朴素,那就是纯靠经验去设置四条线的间距,让字母底部落到第三条线上,然后调整其它几条线的的间距。 但这样的问题很大,只要你修改字体大小,就必须去重新调整线条,虽然不是不能用,但灵活性基本上拉完了。
所以我们需要换一种办法,即使是修改字体大小,也可以让拼音准确的落到四线三格上。
四线三格
让我们看看这个例子,我们可以清楚的看到,现在我们使用了四条线,将区域划分为了三块,分别是上、中、下格。
格子高度
我们要让字母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,然后我们设置一下线宽和颜色。
这样我们就画出四条线了,你还可以考虑让2,3线是虚线,这样可能更符合四线三格。
控制文本基线
现在我们可以加一个child给CustomPaint,就是直接堆叠上去,相当于我们在画布上面。
SizedBox(
width: 300,
child: CustomPaint(
painter: _WritingGuidelinesPainter(
color: Colors.purpleAccent,
gridHeight: 60 * 1.5,
lineWidth: 3,
),
child: Text("a",style: TextStyle(
fontSize: 60
),),
),
)
但是大家会发现问题,这天塌了啊,怎么按照比例写完还是没对齐?
现在让我们请出我们的基线组件:
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就是到第二个格子的底部,也就是第三条线的位置。
大功告成!恭喜你已经成功绘制了一个四线三格,并且还很自然的对齐了文本,当然这个我们选的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,
),
这样我们就得到了一个最简单好用的拼音展示了,这里我们调用了一个扩展函数——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,
),
],
),
)
Warp自动换行
有时候我们需要展示一个列表,进行自动换行,但如果第二行第一个文本很长,就会导致第一行最后空出来了很大的位置,四线三格中断了,不像是作业本那样一行是完整的。
于是我还支持了Warp布局:
child: WritingGuidelinesWrap(
fontSize: 30,
runSpacing: 5,
lineColor: Colors.pink,
children: _pinyinTexts
.map((text) => Text(text, style: TextStyle(fontSize: 30)))
.toList(),
),
现在,我们可以清晰的看到,四线三格在这种情况下仍然完整连续。
更多的用法可以参考 writing_guidelines 组件库。
文末
感谢大家看到这里,如果有错误请大家指出。