Flutter中如何计算一个Container可以完美填充多少文字

要想知道一个 Container 可以填满多少个文字,我们只需算出一行可以填充的文字数量,再算出可以填充的最大行数,将两者相乘就行。

遇到问题先干嘛?当然是用搜索引擎先检索一下答案。我检索到了一篇标题为《Flutter-如何计算文字宽高》的文章(因为我在站内没有搜到这篇文章,所有有兴趣的可以自己用搜索引擎检索查看),这篇文章使用了 TextPainter 来计算出文字的宽高。这里我就用 ParagraphBuilder 来完成计算。这可能并不是最好的,请根据需求酌情参考。

我们有如下一个示例:

显然,如果直接使用 Text 我们什么也做不了,所以我们得使用 CustomPaint 来显示绘制的文字。

class HomeView extends StatelessWidget {
  const HomeView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          clipBehavior: Clip.hardEdge,
          decoration: const BoxDecoration(color: Colors.green),
          child: CustomPaint(
            painter: CustomTextPainter(),
          ),
        ),
      ),
    );
  }
}

class CustomTextPainter extends CustomPainter {

  @override
  void paint(Canvas canvas, Size size) {
    // ...
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => this != oldDelegate;
}

因为不是每次传入的文字大小都是一样的,所以我们需要在外部传入该值。

class HomeView extends StatelessWidget {
  const HomeView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          clipBehavior: Clip.hardEdge,
          decoration: const BoxDecoration(color: Colors.green),
          child: CustomPaint(
            painter: CustomTextPainter(
              fontSize: 16,
              lineHeight: 1,
            ),
          ),
        ),
      ),
    );
  }
}

class CustomTextPainter extends CustomPainter {

  final double fontSize;
  final double lineHeight;

  CustomTextPainter({
    super.repaint,
    required this.fontSize,
    required this.lineHeight,
  });

  @override
  void paint(Canvas canvas, Size size) {
    // ...
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => this != oldDelegate;
}

这里我们指定需要传入两个参数:文字大小和行高,知道这两个参数我们就很容易通过 文字大小 * 文字行高 来计算出文字所占的高度。

计算出了文字所占的高度,就能计算出可以完美显示所有文字的行数。

代码如下:

@override
void paint(Canvas canvas, Size size) {
  ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle());
  paragraphBuilder.pushStyle(
    ui.TextStyle(
      color: Colors.white,
      fontSize: fontSize,
      height: lineHeight,
    ),
  );
  
  double fontHeight = fontSize * lineHeight;
  int fontLine = size.height ~/ fontHeight;
  print('每个文字的高度是:$fontHeight');  // 16
  print('完美呈现的文字行数是:$fontLine');  // 12
}

我们可以显示一段文字来验证下:

@override
void paint(Canvas canvas, Size size) {
  ...
    
  paragraphBuilder.addText('海水梦悠悠' * 100);
  ui.ParagraphConstraints paragraphConstraints = ui.ParagraphConstraints(width: size.width);
  ui.Paragraph paragraph = paragraphBuilder.build();
  paragraph.layout(paragraphConstraints);
  canvas.drawParagraph(paragraph, Offset.zero);
}

一共刚好 12 行。

现在知道了行数,接下来就只要算出一行可以填充几个文字就行。

想要知道一行可以填充几个文字,我们可以先算出一个字所占的宽度。我们先让界面只显示一个字:

@override
void paint(Canvas canvas, Size size) {
  ...
    
  paragraphBuilder.addText('海');
  ui.ParagraphConstraints paragraphConstraints = ui.ParagraphConstraints(width: size.width);
  ui.Paragraph paragraph = paragraphBuilder.build();
  paragraph.layout(paragraphConstraints);
  canvas.drawParagraph(paragraph, Offset.zero);
}
674044642.png

为什么要改成显示一个字?我们虽然无法计算出一个字的宽度,但是可以用 ParagraphcomputeLineMetrics 方法算出每一行文字所占的宽度。

@override
void paint(Canvas canvas, Size size) {
  ...
    
  paragraphBuilder.addText('海');
  ui.ParagraphConstraints paragraphConstraints = ui.ParagraphConstraints(width: size.width);
  ui.Paragraph paragraph = paragraphBuilder.build();
  paragraph.layout(paragraphConstraints);
  List<ui.LineMetrics> lines = paragraph.computeLineMetrics();
  double fontWidth = lines.first.width;
  print("第一行文字所占的宽度:$fontWidth");  // 16.000030517578125
  print("一行可以显示的文字个数:${size.width ~/ fontWidth}");  // 12
  canvas.drawParagraph(paragraph, Offset.zero);
}

为了验证一下是否准确,可以数一下下图填满时的个数:

521

通过以上的内容我们可以计算出,在一个 200*200 的 Container 中,可以完整显示文字大小为 16,行高为 1 的文字共 144 个。

以下是完整代码:

import 'dart:ui' as ui;

import 'package:flutter/material.dart';

class HomeView extends StatelessWidget {
  const HomeView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
          width: 200,
          height: 200,
          clipBehavior: Clip.hardEdge,
          decoration: const BoxDecoration(color: Colors.green),
          child: CustomPaint(
            painter: CustomTextPainter(
              fontSize: 16,
              lineHeight: 1,
            ),
          ),
        ),
      ),
    );
  }
}

class CustomTextPainter extends CustomPainter {
  final double fontSize;
  final double lineHeight;

  CustomTextPainter({
    super.repaint,
    required this.fontSize,
    required this.lineHeight,
  });

  @override
  void paint(Canvas canvas, Size size) {
    ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle());
    paragraphBuilder.pushStyle(
      ui.TextStyle(
        color: Colors.white,
        fontSize: fontSize,
        height: lineHeight,
      ),
    );
    double fontHeight = fontSize * lineHeight;
    int fontLine = size.height ~/ fontHeight;
    print('每个文字的高度是 $fontHeight');
    print('完美呈现的文字行数是 $fontLine');

    paragraphBuilder.addText('海');
    ui.ParagraphConstraints paragraphConstraints = ui.ParagraphConstraints(width: size.width);
    ui.Paragraph paragraph = paragraphBuilder.build();
    paragraph.layout(paragraphConstraints);

    List<ui.LineMetrics> lines = paragraph.computeLineMetrics();
    double fontWidth = lines.first.width;
    print("第一行文字所占的宽度:$fontWidth");
    print("一行可以显示的文字个数:${size.width ~/ fontWidth}");

    canvas.drawParagraph(paragraph, Offset.zero);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => this != oldDelegate;
}

当然,在实际应用中,我们不可能让 “海” 字显示出来,并且只能应用于中文日文之类的文字,如果插入了英文字母数字之类的可能就不准确了。

posted @ 2024-02-26 12:28  菠萝橙子丶  阅读(207)  评论(0编辑  收藏  举报