如果文本溢出,则替换文本

Posted

技术标签:

【中文标题】如果文本溢出,则替换文本【英文标题】:Replace text if it will overflow 【发布时间】:2018-11-12 17:42:05 【问题描述】:

我想知道 Flutter 中是否有办法在原始文本溢出时显示替代文本。

示例:

我默认显示完整日期:January 1, 2019

但是,如果我在小屏幕上并且它会溢出 (January 1...),我想改为显示不同的字符串 (1/1/2019)。

【问题讨论】:

你试过计算文本的宽度吗?应该很容易将其与RenderBox 进行比较。 我的第一个想法是获取现有文本的大小,但我也找不到一个干净的方法来做到这一点。 check this enrty ***.com/questions/49307677/… 获取按钮大小并相应操作 pub.dartlang.org/packages/rect_getter 也检查这个包 【参考方案1】:

我为那些仍在寻找的人找到了一个更简单的解决方案。 我在包上构建了我的解决方案:auto_size_text 2.1.0

import 'package:auto_size_text/auto_size_text.dart';
...
AutoSizeText(
    "Your text that might be too long. I'm super duper long",
    maxLines: 1,
    overflowReplacement: Text("I'm the new (small) replacement!"),
    // minFontSize: 20
),

注意这个包会在触发overflowReplacement之前使文本变小。您可以通过指定 minFontSize 来设置包中允许的最小尺寸。

【讨论】:

【参考方案2】:

我最终选择了一个受@Mantoska 回答启发的解决方案。

import 'package:flutter/widgets.dart';

class OverflowProofText extends StatelessWidget 
  const OverflowProofText(@required this.text, @required this.fallback);

  final Text text;
  final Text fallback;

  @override
  Widget build(BuildContext context) 
    return SizedBox(
        width: double.infinity,
        child: LayoutBuilder(builder: (BuildContext context, BoxConstraints size) 
          final TextPainter painter = TextPainter(
            maxLines: 1,
            textAlign: TextAlign.left,
            textDirection: TextDirection.ltr,
            text: TextSpan(
                style: text.style ?? DefaultTextStyle.of(context).style,
                text: text.data
            ),
          );

          painter.layout(maxWidth: size.maxWidth);

          return painter.didExceedMaxLines ? fallback : text;
        )
    );
  

用法:

OverflowProofText(
  text: Text('January 1, 2019'),
  fallback: Text('1/1/2019', overflow: TextOverflow.fade),
),

【讨论】:

【参考方案3】:

这是一个看起来比 Remi 更简单(或至少更短)的解决方案。

这个想法是您使用 LayoutBuilder 来包装您的小部件,从而获得 BoxConstraints 并使用它您可以使用 TextPainter 来确定文本是否适合给定的 BoxConstraints。

这是一个工作示例:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      title: 'Text Overflow Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text("DEMO")),
        body: TextOverflowDemo(),
      ),
    );
  


class TextOverflowDemo extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    int maxLines = 1;

    return Container(
      color: Colors.white,
      child: ConstrainedBox(
        constraints: const BoxConstraints(maxWidth: 60.0),// set maxWidth to a low value to see the result
        child: LayoutBuilder(builder: (context, size) 
          String text = 'January 1, 2019';
          var exceeded = doesTextFit(text, maxLines, size);

          return Column(children: <Widget>[
            Text(
              exceeded ? '1/1/2019' : text,
              overflow: TextOverflow.ellipsis,
              maxLines: maxLines,
            ),
          ]);
        ),
      ),
    );
  

  bool doesTextFit(String text, int maxLines, BoxConstraints size,
      TextStyle textStyle) 
    TextSpan span;
    if (textStyle == null) 
      span = TextSpan(
        text: text,
      );
     else 
      span = TextSpan(text: text, style: textStyle);
    

    TextPainter tp = TextPainter(
      maxLines: maxLines,
      textAlign: TextAlign.left,
      textDirection: TextDirection.ltr,
      text: span,
    );

    tp.layout(maxWidth: size.maxWidth);

    return tp.didExceedMaxLines;
  

【讨论】:

【参考方案4】:

当前的Text 实现不允许这种逻辑。您将需要使用自定义溢出逻辑覆盖它们的实现。

修改是微不足道的,但请记住,如果发生溢出,您实际上是在计算文本两次。

修改需要在RenderParagraphperformLayout里面进行。

简而言之,就是这样:

performLayout()
   layout();
   if (overflow) 
      layoutWithText(text);
   

这需要自定义RichText 才能使用您的新RenderParagraph。然后是一个新的Text 类,用于使用你的新RichText

相当多的复制粘贴。但幸运的是我会为你做的:D

这是一个将相同的Super long text 渲染两次的示例。一次没有足够的大小,另一次没有限制。

使用以下代码实现:

new Column(
  crossAxisAlignment: CrossAxisAlignment.center,
  mainAxisSize: MainAxisSize.min,
  children: <Widget>[
    new SizedBox(
      width: 70.0,
      child: new Card(
        child: new MyText(
          "Super long text",
          maxLines: 1,
          overflowBuilder: (size) 
            return new TextSpan(
                text: "Hello", style: new TextStyle(color: Colors.red));
          ,
        ),
      ),
    ),
    new Card(
      child: new MyText(
        "Super long text",
        maxLines: 1,
        overflowBuilder: (size) 
          return new TextSpan(
              text: "Hello", style: new TextStyle(color: Colors.red));
        ,
      ),
    ),
  ],
);

这是完整的工作示例(带有RenderParagraph 更改和其他内容)

import 'dart:async';
import 'package:flutter/rendering.dart';
import 'dart:ui' as ui show Gradient, Shader, TextBox;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

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

class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(),
    );
  


class MyHomePage extends StatefulWidget 
  @override
  _MyHomePageState createState() => new _MyHomePageState();


class _MyHomePageState extends State<MyHomePage> 
  final scrollController = new ScrollController();
  final videoRef = Firestore.instance.collection('videos');

  @override
  void initState() 
    super.initState();
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      body: new Center(
        child: new Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            new SizedBox(
              width: 70.0,
              child: new Card(
                child: new MyText(
                  "Super long text",
                  maxLines: 1,
                  overflowBuilder: (size) 
                    return new TextSpan(
                        text: "Hello", style: new TextStyle(color: Colors.red));
                  ,
                ),
              ),
            ),
            new Card(
              child: new MyText(
                "Super long text",
                maxLines: 1,
                overflowBuilder: (size) 
                  return new TextSpan(
                      text: "Hello", style: new TextStyle(color: Colors.red));
                ,
              ),
            ),
          ],
        ),
      ),
    );
  


class OverflowText extends LeafRenderObjectWidget 
  final TextSpan textSpan;
  final TextAlign textAlign;
  final TextDirection textDirection;
  final bool softWrap;
  final TextOverflow overflow;
  final double textScaleFactor;
  final int maxLines;
  final TextOverflowBuilder overflowBuilder;

  OverflowText(
      this.textSpan,
      this.textAlign: TextAlign.start,
      this.textDirection,
      this.softWrap: true,
      this.overflow: TextOverflow.clip,
      this.maxLines,
      this.overflowBuilder,
      this.textScaleFactor: 1.0);

  @override
  RenderObject createRenderObject(BuildContext context) 
    return new OverflowTextRenderObject(this.textSpan,
        textAlign: textAlign,
        textDirection: textDirection ?? Directionality.of(context),
        softWrap: softWrap,
        overflow: overflow,
        textScaleFactor: textScaleFactor,
        maxLines: maxLines,
        overflowBuilder: overflowBuilder);
  

  @override
  void updateRenderObject(
      BuildContext context, OverflowTextRenderObject renderObject) 
    renderObject
      ..text = textSpan
      ..textAlign = textAlign
      ..textDirection = textDirection ?? Directionality.of(context)
      ..softWrap = softWrap
      ..overflow = overflow
      ..textScaleFactor = textScaleFactor
      ..overflowBuilder = overflowBuilder
      ..maxLines = maxLines;
  

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) 
    super.debugFillProperties(properties);
    properties.add(new StringProperty('textSpan', textSpan.toPlainText()));
  


typedef TextSpan TextOverflowBuilder(Size size);

const String _kEllipsis = '\u2026';

/// A render object that displays a paragraph of text
class OverflowTextRenderObject extends RenderBox 
  /// Creates a paragraph render object.
  ///
  /// The [text], [textAlign], [textDirection], [overflow], [softWrap], and
  /// [textScaleFactor] arguments must not be null.
  ///
  /// The [maxLines] property may be null (and indeed defaults to null), but if
  /// it is not null, it must be greater than zero.
  OverflowTextRenderObject(
    TextSpan text, 
    TextAlign textAlign: TextAlign.start,
    @required TextDirection textDirection,
    bool softWrap: true,
    TextOverflow overflow: TextOverflow.clip,
    double textScaleFactor: 1.0,
    int maxLines,
    this.overflowBuilder,
  )  : assert(text != null),
        assert(text.debugAssertIsValid()),
        assert(textAlign != null),
        assert(textDirection != null),
        assert(softWrap != null),
        assert(overflow != null),
        assert(textScaleFactor != null),
        assert(maxLines == null || maxLines > 0),
        _softWrap = softWrap,
        _overflow = overflow,
        _textPainter = new TextPainter(
          text: text,
          textAlign: textAlign,
          textDirection: textDirection,
          textScaleFactor: textScaleFactor,
          maxLines: maxLines,
          ellipsis: overflow == TextOverflow.ellipsis ? _kEllipsis : null,
        );

  TextOverflowBuilder overflowBuilder;

  final TextPainter _textPainter;

  /// The text to display
  TextSpan get text => _textPainter.text;
  set text(TextSpan value) 
    assert(value != null);
    switch (_textPainter.text.compareTo(value)) 
      case RenderComparison.identical:
      case RenderComparison.metadata:
        return;
      case RenderComparison.paint:
        _textPainter.text = value;
        markNeedsPaint();
        break;
      case RenderComparison.layout:
        _textPainter.text = value;
        _overflowShader = null;
        markNeedsLayout();
        break;
    
  

  /// How the text should be aligned horizontally.
  TextAlign get textAlign => _textPainter.textAlign;
  set textAlign(TextAlign value) 
    assert(value != null);
    if (_textPainter.textAlign == value) return;
    _textPainter.textAlign = value;
    markNeedsPaint();
  

  /// The directionality of the text.
  ///
  /// This decides how the [TextAlign.start], [TextAlign.end], and
  /// [TextAlign.justify] values of [textAlign] are interpreted.
  ///
  /// This is also used to disambiguate how to render bidirectional text. For
  /// example, if the [text] is an English phrase followed by a Hebrew phrase,
  /// in a [TextDirection.ltr] context the English phrase will be on the left
  /// and the Hebrew phrase to its right, while in a [TextDirection.rtl]
  /// context, the English phrase will be on the right and the Hebrew phrase on
  /// its left.
  ///
  /// This must not be null.
  TextDirection get textDirection => _textPainter.textDirection;
  set textDirection(TextDirection value) 
    assert(value != null);
    if (_textPainter.textDirection == value) return;
    _textPainter.textDirection = value;
    markNeedsLayout();
  

  /// Whether the text should break at soft line breaks.
  ///
  /// If false, the glyphs in the text will be positioned as if there was
  /// unlimited horizontal space.
  ///
  /// If [softWrap] is false, [overflow] and [textAlign] may have unexpected
  /// effects.
  bool get softWrap => _softWrap;
  bool _softWrap;
  set softWrap(bool value) 
    assert(value != null);
    if (_softWrap == value) return;
    _softWrap = value;
    markNeedsLayout();
  

  /// How visual overflow should be handled.
  TextOverflow get overflow => _overflow;
  TextOverflow _overflow;
  set overflow(TextOverflow value) 
    assert(value != null);
    if (_overflow == value) return;
    _overflow = value;
    _textPainter.ellipsis = value == TextOverflow.ellipsis ? _kEllipsis : null;
    markNeedsLayout();
  

  /// The number of font pixels for each logical pixel.
  ///
  /// For example, if the text scale factor is 1.5, text will be 50% larger than
  /// the specified font size.
  double get textScaleFactor => _textPainter.textScaleFactor;
  set textScaleFactor(double value) 
    assert(value != null);
    if (_textPainter.textScaleFactor == value) return;
    _textPainter.textScaleFactor = value;
    _overflowShader = null;
    markNeedsLayout();
  

  /// An optional maximum number of lines for the text to span, wrapping if necessary.
  /// If the text exceeds the given number of lines, it will be truncated according
  /// to [overflow] and [softWrap].
  int get maxLines => _textPainter.maxLines;

  /// The value may be null. If it is not null, then it must be greater than zero.
  set maxLines(int value) 
    assert(value == null || value > 0);
    if (_textPainter.maxLines == value) return;
    _textPainter.maxLines = value;
    _overflowShader = null;
    markNeedsLayout();
  

  void _layoutText(double minWidth: 0.0, double maxWidth: double.infinity) 
    final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis;
    _textPainter.layout(
        minWidth: minWidth,
        maxWidth: widthMatters ? maxWidth : double.infinity);
  

  void _layoutTextWithConstraints(BoxConstraints constraints) 
    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
  

  @override
  double computeMinIntrinsicWidth(double height) 
    _layoutText();
    return _textPainter.minIntrinsicWidth;
  

  @override
  double computeMaxIntrinsicWidth(double height) 
    _layoutText();
    return _textPainter.maxIntrinsicWidth;
  

  double _computeIntrinsicHeight(double width) 
    _layoutText(minWidth: width, maxWidth: width);
    return _textPainter.height;
  

  @override
  double computeMinIntrinsicHeight(double width) 
    return _computeIntrinsicHeight(width);
  

  @override
  double computeMaxIntrinsicHeight(double width) 
    return _computeIntrinsicHeight(width);
  

  @override
  double computeDistanceToActualBaseline(TextBaseline baseline) 
    assert(!debugNeedsLayout);
    assert(constraints != null);
    assert(constraints.debugAssertIsValid());
    _layoutTextWithConstraints(constraints);
    return _textPainter.computeDistanceToActualBaseline(baseline);
  

  @override
  bool hitTestSelf(Offset position) => true;

  @override
  void handleEvent(PointerEvent event, BoxHitTestEntry entry) 
    assert(debugHandleEvent(event, entry));
    if (event is! PointerDownEvent) return;
    _layoutTextWithConstraints(constraints);
    final Offset offset = entry.localPosition;
    final TextPosition position = _textPainter.getPositionForOffset(offset);
    final TextSpan span = _textPainter.text.getSpanForPosition(position);
    span?.recognizer?.addPointer(event);
  

  bool _hasVisualOverflow = false;
  ui.Shader _overflowShader;

  @visibleForTesting
  bool get debugHasOverflowShader => _overflowShader != null;

  void _performLayout() 
    _layoutTextWithConstraints(constraints);

    final Size textSize = _textPainter.size;
    final bool didOverflowHeight = _textPainter.didExceedMaxLines;
    size = constraints.constrain(textSize);

    final bool didOverflowWidth = size.width < textSize.width;

    _hasVisualOverflow = didOverflowWidth || didOverflowHeight;
    if (_hasVisualOverflow) 
      switch (_overflow) 
        case TextOverflow.clip:
        case TextOverflow.ellipsis:
          _overflowShader = null;
          break;
        case TextOverflow.fade:
          assert(textDirection != null);
          final TextPainter fadeSizePainter = new TextPainter(
            text: new TextSpan(style: _textPainter.text.style, text: '\u2026'),
            textDirection: textDirection,
            textScaleFactor: textScaleFactor,
          )..layout();
          if (didOverflowWidth) 
            double fadeEnd, fadeStart;
            switch (textDirection) 
              case TextDirection.rtl:
                fadeEnd = 0.0;
                fadeStart = fadeSizePainter.width;
                break;
              case TextDirection.ltr:
                fadeEnd = size.width;
                fadeStart = fadeEnd - fadeSizePainter.width;
                break;
            
            _overflowShader = new ui.Gradient.linear(
              new Offset(fadeStart, 0.0),
              new Offset(fadeEnd, 0.0),
              <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
            );
           else 
            final double fadeEnd = size.height;
            final double fadeStart = fadeEnd - fadeSizePainter.height / 2.0;
            _overflowShader = new ui.Gradient.linear(
              new Offset(0.0, fadeStart),
              new Offset(0.0, fadeEnd),
              <Color>[const Color(0xFFFFFFFF), const Color(0x00FFFFFF)],
            );
          
          break;
      
     else 
      _overflowShader = null;
    
  

  @override
  performLayout() 
    _performLayout();
    if (this._hasVisualOverflow && overflowBuilder != null) 
      final replacement = overflowBuilder(size);
      _textPainter.text = replacement;
      _performLayout();
    
  

  @override
  void paint(PaintingContext context, Offset offset) 

    _layoutTextWithConstraints(constraints);
    final Canvas canvas = context.canvas;

    assert(() 
      if (debugRepaintTextRainbowEnabled) 
        final Paint paint = new Paint()
          ..color = debugCurrentRepaintColor.toColor();
        canvas.drawRect(offset & size, paint);
      
      return true;
    ());

    if (_hasVisualOverflow) 
      final Rect bounds = offset & size;
      if (_overflowShader != null) 

        canvas.saveLayer(bounds, new Paint());
       else 
        canvas.save();
      
      canvas.clipRect(bounds);
    
    _textPainter.paint(canvas, offset);
    if (_hasVisualOverflow) 
      if (_overflowShader != null) 
        canvas.translate(offset.dx, offset.dy);
        final Paint paint = new Paint()
          ..blendMode = BlendMode.modulate
          ..shader = _overflowShader;
        canvas.drawRect(Offset.zero & size, paint);
      
      canvas.restore();
    
  

  Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) 
    assert(!debugNeedsLayout);
    _layoutTextWithConstraints(constraints);
    return _textPainter.getOffsetForCaret(position, caretPrototype);
  


  List<ui.TextBox> getBoxesForSelection(TextSelection selection) 
    assert(!debugNeedsLayout);
    _layoutTextWithConstraints(constraints);
    return _textPainter.getBoxesForSelection(selection);
  

  TextPosition getPositionForOffset(Offset offset) 
    assert(!debugNeedsLayout);
    _layoutTextWithConstraints(constraints);
    return _textPainter.getPositionForOffset(offset);
  


  TextRange getWordBoundary(TextPosition position) 
    assert(!debugNeedsLayout);
    _layoutTextWithConstraints(constraints);
    return _textPainter.getWordBoundary(position);
  


  Size get textSize 
    assert(!debugNeedsLayout);
    return _textPainter.size;
  

  @override
  void describeSemanticsConfiguration(SemanticsConfiguration config) 
    super.describeSemanticsConfiguration(config);
    config
      ..label = text.toPlainText()
      ..textDirection = textDirection;
  

  @override
  List<DiagnosticsNode> debugDescribeChildren() 
    return <DiagnosticsNode>[
      text.toDiagnosticsNode(
          name: 'text', style: DiagnosticsTreeStyle.transition)
    ];
  

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) 
    super.debugFillProperties(properties);
    properties.add(new EnumProperty<TextAlign>('textAlign', textAlign));
    properties
        .add(new EnumProperty<TextDirection>('textDirection', textDirection));
    properties.add(new FlagProperty('softWrap',
        value: softWrap,
        ifTrue: 'wrapping at box width',
        ifFalse: 'no wrapping except at line break characters',
        showName: true));
    properties.add(new EnumProperty<TextOverflow>('overflow', overflow));
    properties.add(new DoubleProperty('textScaleFactor', textScaleFactor,
        defaultValue: 1.0));
    properties.add(new IntProperty('maxLines', maxLines, ifNull: 'unlimited'));
  



class MyText extends StatelessWidget 
  const MyText(this.data,
      Key key,
      this.style,
      this.textAlign,
      this.textDirection,
      this.softWrap,
      this.overflow,
      this.textScaleFactor,
      this.maxLines,
      this.overflowBuilder)
      : assert(data != null),
        textSpan = null,
        super(key: key);

  const MyText.rich(this.textSpan,
      Key key,
      this.style,
      this.textAlign,
      this.textDirection,
      this.softWrap,
      this.overflow,
      this.textScaleFactor,
      this.maxLines,
      this.overflowBuilder)
      : assert(textSpan != null),
        data = null,
        super(key: key);

  final String data;
  final TextSpan textSpan;

  final TextStyle style;

  final TextAlign textAlign;


  final TextDirection textDirection;
  final bool softWrap;

  final TextOverflow overflow;

  final double textScaleFactor;

  final TextOverflowBuilder overflowBuilder;

  final int maxLines;

  @override
  Widget build(BuildContext context) 
    final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
    TextStyle effectiveTextStyle = style;
    if (style == null || style.inherit)
      effectiveTextStyle = defaultTextStyle.style.merge(style);
    return new OverflowText(
      textAlign: textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start,
      textDirection:
          textDirection, // RichText uses Directionality.of to obtain a default if this is null.
      softWrap: softWrap ?? defaultTextStyle.softWrap,
      overflow: overflow ?? defaultTextStyle.overflow,
      overflowBuilder: overflowBuilder,
      textScaleFactor: textScaleFactor ??
          MediaQuery.of(context, nullOk: true)?.textScaleFactor ??
          1.0,
      maxLines: maxLines ?? defaultTextStyle.maxLines,
      textSpan: new TextSpan(
        style: effectiveTextStyle,
        text: data,
        children: textSpan != null ? <TextSpan>[textSpan] : null,
      ),
    );
  


【讨论】:

对我不起作用。出于某种原因,“_hasVisualOverflow”总是错误的

以上是关于如果文本溢出,则替换文本的主要内容,如果未能解决你的问题,请参考以下文章

InkWell 中的文本应用溢出不工作

html文本溢出

如何在文本中间显示文本溢出省略号

卡内的 TableCell 文本在 React 中溢出

文本溢出处理

引导模式中的文本溢出省略号