如何在颤抖中获取文本小部件的大小

时间:2018-10-05 06:44:47

标签: dart flutter

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

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

因此,我需要在Widget build(BuildContext context)之前获取我的文本小部件的宽度和高度。

实际上,我正在使用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

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

bubble.dart:

// 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 ...

气泡效果:

enter image description here

7 个答案:

答案 0 :(得分:22)

抱歉。 这不是该主题问题的直接答案! 但是,如果有人需要获取文本小部件的大小,则此方法可以提供帮助。它帮助我创建了自定义菜单小部件。

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;
  }
}

答案 1 :(得分:9)

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

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

答案 2 :(得分:4)

一个简单的例子:

有关其工作原理,请参见嵌入式注释。

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

enter image description here

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}'),
      );
}

enter image description here

答案 3 :(得分:3)

多行文本高度(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;
  }
}

答案 4 :(得分:0)

源自昆特·鲍勃(GünterZö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];
}

答案 5 :(得分:0)

我发现了另一种不使用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();

答案 6 :(得分:0)

GünterZö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);
}