【问题标题】:How can I get the size of the Text Widget in flutter如何在颤动中获取文本小部件的大小
【发布时间】:2019-03-10 14:46:19
【问题描述】:

我为Text 的内容的背景绘制了一个形状。

我希望背景自动缩放文本,即使 softWrap 是真的。

所以,我需要在 Widget build(BuildContext context) 之前获取我的 Text Widget 的宽度和高度。

其实我是在用flutter模拟iOS消息的聊天气泡效果。这里是iOS版教程。 Creating a Chat Bubble 。

核心代码如下:

let label =  UILabel()
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 18)
label.textColor = .white
label.text = text

let constraintRect = CGSize(width: 0.66 * view.frame.width,
                            height: .greatestFiniteMagnitude)
let boundingBox = text.boundingRect(with: constraintRect,
                                    options: .usesLineFragmentOrigin,
                                    attributes: [.font: label.font],
                                    context: nil)
label.frame.size = CGSize(width: ceil(boundingBox.width),
                          height: ceil(boundingBox.height))

let bubbleSize = CGSize(width: label.frame.width + 28,
                             height: label.frame.height + 20)

let width = bubbleSize.width
let height = bubbleSize.height

==========================================
解决方案
这是我的解决方案。

泡泡飞镖:

// Define a CustomPainter to paint the bubble background.
class BubblePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Color(0xff188aff)
      ..style = PaintingStyle.fill;
    final Path bubble = Path()
      ..moveTo(size.width - 22.0, size.height)
      ..lineTo(17.0, size.height)
      ..cubicTo(
          7.61, size.height, 0.0, size.height - 7.61, 0.0, size.height - 17.0)
      ..lineTo(0.0, 17.0)
      ..cubicTo(0.0, 7.61, 7.61, 0.0, 17.0, 0.0)
      ..lineTo(size.width - 21, 0.0)
      ..cubicTo(size.width - 11.61, 0.0, size.width - 4.0, 7.61,
          size.width - 4.0, 17.0)
      ..lineTo(size.width - 4.0, size.height - 11.0)
      ..cubicTo(size.width - 4.0, size.height - 1.0, size.width, size.height,
          size.width, size.height)
      ..lineTo(size.width + 0.05, size.height - 0.01)
      ..cubicTo(size.width - 4.07, size.height + 0.43, size.width - 8.16,
          size.height - 1.06, size.width - 11.04, size.height - 4.04)
      ..cubicTo(size.width - 16.0, size.height, size.width - 19.0, size.height,
          size.width - 22.0, size.height)
      ..close();
    canvas.drawPath(bubble, paint);
  }

  @override
  bool shouldRepaint(BubblePainter oldPainter) => true;
}

// This is my custom RenderObject.
class BubbleMessage extends SingleChildRenderObjectWidget {
  BubbleMessage({
    Key key,
    this.painter,
    Widget child,
  }) : super(key: key, child: child);

  final CustomPainter painter;

  @override
  RenderCustomPaint createRenderObject(BuildContext context) {
    return RenderCustomPaint(
      painter: painter,
    );
  }

  @override
  void updateRenderObject(
      BuildContext context, RenderCustomPaint renderObject) {
    renderObject..painter = painter;
  }
}

像这样使用BubbleMessage 小部件:

import 'bubble.dart' 

...code ... 

BubbleMessage(
  painter: BubblePainter(),
  child: Container(
    constraints: BoxConstraints(
      maxWidth: 250.0,
      minWidth: 50.0,
    ),
    padding: EdgeInsets.symmetric(horizontal: 15.0, vertical: 6.0),
    child: Text(
      'your text variable',
      softWrap: true,
      style: TextStyle(
        fontSize: 16.0,
      ),
    ),
  ),
),

...code ...

气泡效果:

【问题讨论】:

标签: dart flutter


【解决方案1】:

我很抱歉。 这不是对主题问题的直接回答! 但是如果有人需要获取 Text 小部件的大小——这个方法会有所帮助。它帮助我创建了自定义菜单小部件。

class TextSized extends StatelessWidget {
  const TextSized({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final String text = "Text in one line";
    final TextStyle textStyle = TextStyle(
      fontSize: 30,
      color: Colors.white,
    );
    final Size txtSize = _textSize(text, textStyle);

    // This kind of use - meaningless. It's just an example.
    return Container(
      color: Colors.blueGrey,
      width: txtSize.width,
      height: txtSize.height,
      child: Text(
        text,
        style: textStyle,
        softWrap: false,
        overflow: TextOverflow.clip,
        maxLines: 1,
      ),
    );
  }

  // Here it is!
  Size _textSize(String text, TextStyle style) {
    final TextPainter textPainter = TextPainter(
        text: TextSpan(text: text, style: style), maxLines: 1, textDirection: TextDirection.ltr)
      ..layout(minWidth: 0, maxWidth: double.infinity);
    return textPainter.size;
  }
}

【讨论】:

  • 这正是我想要的!太棒了
  • 这是最好的答案。非常感谢!!!!
  • 非常感谢!!!!
【解决方案2】:

其他答案的问题是,如果您使用Text 小部件显示您的文本并使用测量结果约束它而不考虑默认字体系列和比例因子,那么您将得到错误的结果,因为Text 小部件正在使用设备的@默认情况下为 987654323@ 并将其传递给其中的 RichText 小部件。这是测量文本大小的正确代码:

final Size size = (TextPainter(
        text: TextSpan(text: text, style: textStyle),
        maxLines: 1,
        textScaleFactor: MediaQuery.of(context).textScaleFactor,
        textDirection: TextDirection.ltr)
      ..layout())
    .size;

【讨论】:

  • 这应该是正确的答案。需要考虑 textScaleFactor 否则结果在所有设备上都会出错,除了那些使用默认 textScaleFactor 的设备。
  • 谢谢!这是我问题的完美解决方案。
  • 这似乎不适用于多行文本,即使将 maxLines 更改为假设 10。这是为什么呢?
  • 不工作是什么意思?你得到什么结果? @w461
  • 当文本在屏幕上跨越多行时,size 如上定义提供文本在单行中呈现时的宽度和高度(导致溢出)。我的解决方法是height = (textSpanWidth / screenWidth).ceilToDouble(),但也许有更可靠的方法
【解决方案3】:

我找到了另一种不使用context的方法:

final constraints = BoxConstraints(
  maxWidth: 800.0, // maxwidth calculated
  minHeight: 0.0,
  minWidth: 0.0,
);

RenderParagraph renderParagraph = RenderParagraph(
  TextSpan(
    text: text,
    style: TextStyle(
      fontSize: fontSize,
    ),
  ),
  textDirection: ui.TextDirection.ltr,
  maxLines: 1,
);
renderParagraph.layout(constraints);
double textlen = renderParagraph.getMinIntrinsicWidth(fontSize).ceilToDouble();

【讨论】:

  • 给我一个错误:The height argument to getMinIntrinsicWidth was null.
【解决方案4】:

一个简单的例子:

有关其工作原理,请参阅内联 cmets。

来自https://github.com/flutter/flutter/issues/23247的灵感

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Calc Text Size',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Calc Text Size'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const String loremIpsum =
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod '
      'tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim '
      'veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea '
      'commodo consequat. Duis aute irure dolor in reprehenderit in voluptate '
      'velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint '
      'occaecat cupidatat non proident, sunt in culpa qui officia deserunt '
      'mollit anim id est laborum.';

  @override
  Widget build(BuildContext context) {
    final mq = MediaQuery.of(context);
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            SizedBox(
              height: mq.size.height,
              width: 240.0,
              child: ListView(
                padding: EdgeInsets.all(4.0),
                children: <Widget>[
                  Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.orange),
                    ),
                    child: Bubble(
                      text: TextSpan(
                        text: loremIpsum,
                        style: Theme.of(context).textTheme.body1,
                      ),
                    ),
                  ),
                  Container(
                    decoration: BoxDecoration(
                      border: Border.all(color: Colors.orange, width: 2.0),
                    ),
                    padding: EdgeInsets.symmetric(horizontal: 2.0),
                    child: Bubble(
                      text: TextSpan(
                        text: loremIpsum,
                        style: Theme.of(context).textTheme.body1,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class Bubble extends StatefulWidget {
  Bubble({@required this.text});

  final TextSpan text;

  @override
  _BubbleState createState() => new _BubbleState();
}

class _BubbleState extends State<Bubble> {
  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      // The text to render
      final textWidget = Text.rich(widget.text);

      // Calculate the left, top, bottom position of the end of the last text
      // line.
      final lastBox = _calcLastLineEnd(context, constraints);

      // Calculate whether the timestamp fits into the last line or if it has
      // to be positioned after the last line.
      final fitsLastLine =
          constraints.maxWidth - lastBox.right > Timestamp.size.width + 10.0;

      return Stack(
        children: [
          // Ensure the stack is big enough to render the text and the
          // timestamp.
          SizedBox.fromSize(
              size: Size(
                constraints.maxWidth,
                (fitsLastLine ? lastBox.top : lastBox.bottom) +
                    10.0 +
                    Timestamp.size.height,
              ),
              child: Container()),
          // Render the text.
          textWidget,
          // Render the timestamp.
          Positioned(
            left: constraints.maxWidth - (Timestamp.size.width + 10.0),
            top: (fitsLastLine ? lastBox.top : lastBox.bottom) + 5.0,
            child: Timestamp(DateTime.now()),
          ),
        ],
      );
    });
  }

  // Calculate the left, top, bottom position of the end of the last text
  // line.
  TextBox _calcLastLineEnd(BuildContext context, BoxConstraints constraints) {
    final richTextWidget = Text.rich(widget.text).build(context) as RichText;
    final renderObject = richTextWidget.createRenderObject(context);
    renderObject.layout(constraints);
    final lastBox = renderObject
        .getBoxesForSelection(TextSelection(
            baseOffset: 0, extentOffset: widget.text.toPlainText().length))
        .last;
    return lastBox;
  }
}

class Timestamp extends StatelessWidget {
  Timestamp(this.timestamp);

  final DateTime timestamp;

  /// This size could be calculated similarly to the way the text size in
  /// [Bubble] is calculated instead of using magic values.
  static final Size size = Size(60.0, 25.0);

  @override
  Widget build(BuildContext context) => Container(
        padding: EdgeInsets.all(3.0),
        decoration: BoxDecoration(
          color: Colors.greenAccent,
          border: Border.all(color: Colors.yellow),
        ),
        child:
            Text('${timestamp.hour}:${timestamp.minute}:${timestamp.second}'),
      );
}

【讨论】:

  • 在同一行上,我们是否有办法测量具有多个小部件的列?只是想在构建之前知道柱子的高度。这是为了计算 GridView 的纵横比,以便正确显示所有内容。
  • 经历过了。但我的理解是,对于这些示例,小部件需要位于树中。对我来说,只有在我得到计算后,小部件才会出现在树中。
  • 我认为没有办法在不渲染的情况下获得维度。您可以使其不可见,但约束必须是真实的,否则您可能会得到与实际约束无关的尺寸。
  • @GünterZöchbauer 感谢您的示例。这几天我一直在寻找这样的东西。没有这个,我的聊天应用程序看起来不像 whatsapp 和电报使用的传统布局。我已经在我的应用程序中实现了这一点,但我的挑战是我的应用程序中 chatDetail 页面的渲染时间比100ms 增加了一个庞大的2000ms (仍然因为它很慢而感到内疚)。推荐这个我是一个有很多消息要呈现的聊天应用程序吗?
  • 很难说。也许不要处理无论如何都不可见的消息。尝试缓存文本大小结果。只要布局不改变,它们就不会改变(侧边菜单显示/隐藏,纵向/横向旋转,...)
【解决方案5】:

多行文字高度(Dmitry_Kovalov 的修改变体)

import 'package:flutter/cupertino.dart';

extension StringExtension on String {
  double textHeight(TextStyle style, double textWidth) {
    final TextPainter textPainter = TextPainter(
      text: TextSpan(text: this, style: style),
      textDirection: TextDirection.ltr,
      maxLines: 1,
    )..layout(minWidth: 0, maxWidth: double.infinity);

    final countLines = (textPainter.size.width / textWidth).ceil();
    final height = countLines * textPainter.size.height;
    return height;
  }
}

【讨论】:

    【解决方案6】:

    来自 Günter Zöchbauer 的启发

    List<bool> _calcLastLineEnd(String msg) {
      // self-defined constraint
      final constraints = BoxConstraints(
        maxWidth: 800.0, // maxwidth calculated
        minHeight: 30.0,
        minWidth: 80.0,
      );
      final richTextWidget =
          Text.rich(TextSpan(text: msg)).build(context) as RichText;
      final renderObject = richTextWidget.createRenderObject(context);
      renderObject.layout(constraints);
      final boxes = renderObject.getBoxesForSelection(TextSelection(
          baseOffset: 0, extentOffset: TextSpan(text: msg).toPlainText().length));
      bool needPadding = false, needNextline = false;
      if (boxes.length < 2 && boxes.last.right < 630) needPadding = true;
      if (boxes.length < 2 && boxes.last.right > 630) needNextline = true;
      if (boxes.length > 1 && boxes.last.right > 630) needNextline = true;
      return [needPadding, needNextline];
    }
    

    【讨论】:

    • 非常有帮助。我用的是泰文字符,其宽度和高度可以变化很大。有趣的是,所有角色的高度从未改变。尽管有些人的高度是其他人的两倍。幸运的是,宽度值确实会根据字符的可观察宽度而有所不同。
    【解决方案7】:

    Günter Zöchbauer 解决方案的修改变体,可以使用文本溢出

    TextBox textBox(BuildContext context, String text, TextStyle textStyle,
        int maxLines, TextOverflow overflow, BoxConstraints constraints) {
      final textSpan = TextSpan(
        text: text,
        style: textStyle,
      );
      final richTextWidget = Text.rich(
        textSpan,
        maxLines: maxLines,
        overflow: overflow,
      ).build(context) as RichText;
      final renderObject = richTextWidget.createRenderObject(context);
      renderObject.layout(constraints);
      final boxesForSelection = renderObject.getBoxesForSelection(TextSelection(
          baseOffset: 0, extentOffset: richTextWidget.text.toPlainText().length));
      if (boxesForSelection.length == 0)
        return TextBox.fromLTRBD(0.0, 0.0, 0.0, 0.0, TextDirection.ltr);
      final List<double> widths = List();
      boxesForSelection.forEach((box) {
        widths.add(box.right);
      });
      widths.sort((a, b) => a.compareTo(b));
      return TextBox.fromLTRBD(
          0.0, 0.0, widths.last, boxesForSelection.last.bottom, TextDirection.ltr);
    }
    

    【讨论】:

      猜你喜欢
      • 2023-04-10
      • 2019-11-23
      • 2020-03-01
      • 2021-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-25
      • 2020-11-02
      相关资源
      最近更新 更多