如果文本溢出,则替换文本
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
实现不允许这种逻辑。您将需要使用自定义溢出逻辑覆盖它们的实现。
修改是微不足道的,但请记住,如果发生溢出,您实际上是在计算文本两次。
修改需要在RenderParagraph
的performLayout
里面进行。
简而言之,就是这样:
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”总是错误的以上是关于如果文本溢出,则替换文本的主要内容,如果未能解决你的问题,请参考以下文章