首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如果文本溢出,则将其替换

如果文本溢出,则将其替换
EN

Stack Overflow用户
提问于 2018-06-03 10:23:43
回答 4查看 2.2K关注 0票数 5

我想知道在Flutter中是否有一种方法可以在原始文本溢出时显示替代文本。

示例:

默认情况下,我会显示完整的日期:January 1, 2019

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

EN

回答 4

Stack Overflow用户

回答已采纳

发布于 2018-12-27 10:59:56

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

代码语言:javascript
复制
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;
        })
    );
  }
}

用法:

代码语言:javascript
复制
OverflowProofText(
  text: Text('January 1, 2019'),
  fallback: Text('1/1/2019', overflow: TextOverflow.fade),
),
票数 1
EN

Stack Overflow用户

发布于 2018-06-04 03:19:57

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

修改是微不足道的,但请记住,在溢出的情况下,您实际上是在计算文本两次。

修改需要在RenderParagraphperformLayout中完成。

简而言之,就像这样:

代码语言:javascript
复制
performLayout()
   layout();
   if (overflow) {
      layoutWithText(text);
   }
}

这就需要一个自定义的RichText来使用您的新RenderParagraph。然后创建一个新的Text类,以使用新的RichText

相当多的复制粘贴。但幸运的是,我会为你做这件事

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

使用以下代码实现:

代码语言:javascript
复制
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更改和其他内容)

代码语言:javascript
复制
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,
      ),
    );
  }

}
票数 4
EN

Stack Overflow用户

发布于 2018-10-04 14:31:02

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

其思想是使用LayoutBuilder包装您的小部件,从而获得BoxConstraints,并使用TextPainter确定文本是否适合给定的BoxConstraints。

下面是一个有效的示例:

代码语言:javascript
复制
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;
  }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50662768

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档