Replace text if it will overflow - text
I'm wondering if there's a way in Flutter to show alternate text if the original text will overflow.
Example:
I'm by default showing a full date: January 1, 2019.
However, if I'm on a small screen and it would overflow (January 1...), I'd like to instead display a different string (1/1/2019).
The current Text implementation doesn't allow for this kind of logic. You will need to override their implementation with custom overflow logic.
The modification is trivial, but bear in mind that in case of an overflow you're actually computing the text twice.
The modification needs to be done inside RenderParagraph's performLayout.
In short, something like that :
performLayout()
layout();
if (overflow) {
layoutWithText(text);
}
}
Which then requires a custom RichText to use your new RenderParagraph. And then a new Text class, to use your new RichText.
Quite a lot of copy paste. But fortunately I'll do it for you :D
Here's an example rendering the same Super long text twice. Once without enough size, the other with no limitation.
Achieved using the following code :
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));
},
),
),
],
);
And here's the fully working example (with RenderParagraph changes and stuff)
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,
),
);
}
}
I ended up going with a solution inspired by #Mantoska's answer.
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;
})
);
}
}
Usage:
OverflowProofText(
text: Text('January 1, 2019'),
fallback: Text('1/1/2019', overflow: TextOverflow.fade),
),
Here is a solution that look simpler (or at least shorter) than Remi's.
The idea is that you use LayoutBuilder to wrap your widget, thus getting the BoxConstraints and using that you can use TextPainter to determine if the text would fit in the given BoxConstraints.
Here is a working example:
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;
}
}
I found a bit easier solution for the people out there still looking.
I built my solution on the package: 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
),
Note this package will make the text smaller as well before triggering overflowReplacement. You can set the minimum allowed size in the package by specifying minFontSize.
Related
Among the whole API response string I wants to get only particular word in flutter how to do it?
Below is the response where I am getting image path.the file can be pdf or jpeg or any other format. for image i am showing it in dialogue and for pdf I have to download it to the user's device.For that first I have to check where the file is pdf or not for that i wants to get the extension of file format like pdf or jpeg from whole path (shown in 0th and 1st). How to do it using spilt string I have tried several ways but unable to get the result.Please guide me regarding it. 0:"https://xuriti-prod-kyc.s3.ap-south-1.amazonaws.com/27AAACB2100P1ZX/1675230131954-download-%281%29.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXALYDECMNE2HOZS7%2F20230216%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=20230216T063528Z&X-Amz-Expires=900&X-Amz-Signature=59204e81b56cd325de1531a1e6c6a67c5030c76c0516b4719e40986e1ef1d917&X-Amz-SignedHeaders=host" 1:"https://xuriti-prod-kyc.s3.ap-south-1.amazonaws.com/27AAACB2100P1ZX/1675230131972-download.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXALYDECMNE2HOZS7%2F20230216%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=20230216T063528Z&X-Amz-Expires=900&X-Amz-Signature=867311e96282f231406f46d5f2a5d11befe985b0b3a0ab161f69dfb3465b50a9&X-Amz-SignedHeaders=host" Below is my code where i tried to split and get the file format: List imgfiles = []; #override void initState() { init(); super.initState(); } Future init() async { dynamic companyId = getIt<SharedPreferences>().getString('companyId'); //final docs = DioClient().KycDetails(companyId); dynamic responseData = await getIt<DioClient>().KycDetails(companyId); final details = responseData['data']; Aadhar Docdetails = Aadhar.fromJson(details['aadhar']); setState(() { List<String> imgfiles = Docdetails.files; this.imgfiles = imgfiles; }); } Padding( padding: EdgeInsets.only( left: w1p * 3, right: w1p * 6, ), child: SizedBox( width: maxWidth, height: 50, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: imgfiles.length, itemBuilder: (context, index) { String doc = imgfiles[index]; print('the whole filepath >>>>>>>>$doc'); String doc1 = doc.split('jpeg').toString(); print('doc1.>>>>>>>>$doc1'); return GestureDetector( onTap: () { showDialog( context: context, builder: (context) { return Dialog( child: Container( width: 220, height: 200, child: Image.network( '$doc', fit: BoxFit.cover, ), ), ); }); }, child: imageDialog()); }, ), //_checkController(); ), ),
just use split and substring String getExtension(url){ url = url.split('?')[0]; url = url.split('/').last; return url.contains('.') ? url.substring(url.lastIndexOf('.')+1) : ""; } Usage, const url = "https://xuriti-prod-kyc.s3.ap-south-1.amazonaws.com/27AAACB2100P1ZX/1675230131954-download-%281%29.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXALYDECMNE2HOZS7%2F20230216%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=20230216T063528Z&X-Amz-Expires=900&X-Amz-Signature=59204e81b56cd325de1531a1e6c6a67c5030c76c0516b4719e40986e1ef1d917&X-Amz-SignedHeaders=host"; final extension = getExtension(url); // return 'jpeg' if (extension == "jpeg"){ // show image } else if(extension == "pdf") { // show download option }
If you are sure response will have consistent format you can try this. Also to be sure you may add a couple of checks on each step to avoid exceptions. final String input = 'https://xuriti-prod-kyc.s3.ap-south-1.amazonaws.com/27AAACB2100P1ZX/1675230131972-download.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAXALYDECMNE2HOZS7%2F20230216%2Fap-south-1%2Fs3%2Faws4_request&X-Amz-Date=20230216T063528Z&X-Amz-Expires=900&X-Amz-Signature=867311e96282f231406f46d5f2a5d11befe985b0b3a0ab161f69dfb3465b50a9&X-Amz-SignedHeaders=host'; final res = input.split('/').last.split('?').first.split('.').last;
Instead of splitting you can use regex here: String getExtension(url){ final regexJpeg = RegExp(r'\.jpeg\?*'); final regexPdf = RegExp(r'\.pdf\?*'); if(regexJpeg.hasMatch(url)){ return 'jpeg'; } else if(regexPdf.hasMatch(url)){ return 'pdf'; } else{ return 'unknown'; } }
Flutter: Animations badly slow down disk write speed
Not sure if this is a bug or just how the things work, or me doing something wrong. I'm making an app which can parse text files for some data and then store it in a sqlite database. At some point I decided to add basic spinner to indicate that there is parsing in progress, cause it usually takes few seconds on an average file. So I added a ternary operator which renders CircularProgressIndicator() if isBusy variable is set to true. And my parsing time suddenly increased to about 10 times of what it was before. That's on Linux. On Android it also slows down process but only for additional ~70%. I checked few things and it looks like any animation causes this problem, e.g. AnimatedContainer. Steps to reproduce: Run the code below, notice the completion time in dialog. Uncomment CircularProgressIndicator() or AnimatedContainer block and run the code again. Notice how much longer it takes now. import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; const Color darkBlue = Color.fromARGB(255, 18, 32, 47); void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { #override Widget build(BuildContext context) { return MaterialApp( theme: ThemeData.dark().copyWith( scaffoldBackgroundColor: darkBlue, ), debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: MyWidget(), ), ), ); } } class MyWidget extends StatefulWidget { #override State<MyWidget> createState() => _MyWidget(); } class _MyWidget extends State<MyWidget> { var _width = 50.0; var _height = 50.0; void startHeavyOperation() async { final directory = await getApplicationDocumentsDirectory(); var myFile = File('${directory.path}/test.txt'); const max = 10000; var x = 0; setState(() { _width = 300; _height = 300; }); final startTime = DateTime.now(); while (x < max) { await myFile.writeAsString('$x\n', mode: FileMode.append); x += 1; } final endTime = DateTime.now(); final durationInMilliseconds = endTime.difference(startTime).inMilliseconds; showDialog( context: context, builder: (ctx) => AlertDialog( title: Text('It took $durationInMilliseconds ms'), ), ); } #override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () => startHeavyOperation(), child: const Text('startHeavyOperation'), ), // AnimatedContainer( // duration: Duration(seconds: 10), // child: Text('123'), // color: Colors.red, // width: _width, // height: _height, // ), // CircularProgressIndicator(), ], ), ); } } My question is: is there a way to use animations w/o slowing disk writes?
Yeah - I suspect it is the animation indeed. Try putting the heavy operation in an isolate - I suspect that will speed things up (on the device). Here's an example.
That while statement is very expensive even without the animation. Putting in a separate isolate will save you some time. Here is a quick modification to your code example. compute will give you a future. void startHeavyOperation(String path) async { var myFile = File('$path/test.txt'); const max = 10000; var x = 0; final startTime = DateTime.now(); while (x < max) { await myFile.writeAsString('$x\n', mode: FileMode.append); x += 1; } final endTime = DateTime.now(); final durationInMilliseconds = endTime.difference(startTime).inMilliseconds; print('It took $durationInMilliseconds ms'); } class MyWidget extends StatefulWidget { #override State<MyWidget> createState() => _MyWidget(); } class _MyWidget extends State<MyWidget> { var _width = 50.0; var _height = 50.0; #override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () async { final directory = await getApplicationDocumentsDirectory(); compute(startHeavyOperation, directory.path); }, child: const Text('startHeavyOperation'), ), // AnimatedContainer( // duration: Duration(seconds: 10), // child: Text('123'), // color: Colors.red, // width: _width, // height: _height, // ), // CircularProgressIndicator(), ], ), ); } }
Dart - Dynamically creating checkboxes with listview is blank unless refreshed
I'm trying to make dynamically created checkboxes with a ListView. If I restart the simulator and go to my search page, the checkboxes aren't there. If I "hot reload" the simulator (ctrl + S) the checkboxes show up. If I go back to the menu and come back to the search page, the checkboxes are gone. If I click on "search" to search for the recipes in my database, the checkboxes reappear. I don't know what's going on here. Here is my categories map: Map<String, bool> categoryMap = {}; var checkedCategories = []; void findCategories() async { int responseCode = await categories(); print(responseCode); if(responseCode == 200){ newCategories = categoriesList; for(int i = 0; i < newCategories.length; i++){ categoryMap.putIfAbsent(newCategories[i]['name'].toString(), () => false); } print("new categories of length ${newCategories.length} are: " + newCategories[1]['name'].toString()); print("categoryMap is: " + categoryMap.toString()); } else { print("Error: unauthorized"); } } getCheckedCategories(){ categoryMap.forEach((key, value) { if(value == true) { checkedCategories.add(key); } }); // Printing all selected items on Terminal screen. print(checkedCategories); print(categoryMap.keys); // Here you will get all your selected Checkbox items. // Clear array after use. checkedCategories.clear(); } #override void initState() { super.initState(); findCategories(); print('hi there'); } And here is where I am trying to show the checkboxes: child: Column( children: [ SizedBox(height: 40.0,), new ListView( scrollDirection: Axis.vertical, shrinkWrap: true, children: categoryMap.keys.map((String key) { //print("here is a new checkbox!"); return new CheckboxListTile( title: new Text(key), value: categoryMap[key], activeColor: Colors.pink, checkColor: Colors.white, onChanged: (bool value) { setState(() { categoryMap[key] = value; }); }, ); }).toList(), ), isLoading ? Center( child: CircularProgressIndicator(), ) : new RaisedButton( child: Text('Search'), color: Colors.orange[600], textColor: Colors.white, onPressed: () async { setState(() { isLoading = true; //Data is loading }); /*int response_code = await categories(); print(response_code);*/ clearResults(); await getRecipe(searchController.text); Navigator.push(context, MaterialPageRoute(builder: (context)=>RecipeResultsPage())); /*if(response_code == 200){ await getRecipe(searchController.text); Navigator.push(context, MaterialPageRoute(builder: (context)=>RecipeResultsPage())); } else { print("Error: unauthorized"); }*/ }, ), Here is a demonstration of the bug (notice how the checkboxes disappear if I go back to the menu and come back to the search page): Also notice how the checkboxes appear if I click on the search button
Try wrapping the code where you update categoryMap in a setState like this: setState(() { for(int i = 0; i < newCategories.length; i++){ categoryMap.putIfAbsent(newCategories[i]['name'].toString(), () => false); } });
change single char style inside string when it will be clicked flutter
for example i have word welcome i want to change color of c when ever user clicks the letter c i get the position but i can not chnage this position to an index of the string. i used RichText for using different style in one string and this is the code import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:positioned_tap_detector/positioned_tap_detector.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { // This widget is the root of your application. #override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; #override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { RichText rich; String word; String letter; String pressedLetter; Color color = Colors.black; TapPosition _position = TapPosition(Offset.zero, Offset.zero); int x=0; int y=0; #override Widget build(BuildContext context) { word = 'ویژدان'; letter = 'د'; var blackRt = new GestureDetector( child: Text( word, style: TextStyle(fontSize: 25), ), onTap: () { setState(() { color = Colors.red; }); }); var redRt = useDifferentDesignInAString(word, Colors.red); Widget currentRT = new Container(); if (color == Colors.black) currentRT = blackRt; else if (color == Colors.red) currentRT = redRt; return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: PositionedTapDetector( onTap: _onTap, child: currentRT, ), ), ); } void _onTap(TapPosition position) { setState((){ _position = position; x=position.global.dx.toInt(); y=position.global.dy.toInt(); String pressed=word[offset]; }); } RichText useDifferentDesignInAString(String word, Color color) { String firstPart = ""; String remain = ""; RichText richText; if (word.startsWith(letter)) { richText = RichText( text: TextSpan( text: letter, style: TextStyle(color: color, fontSize: 25), children: <TextSpan>[ TextSpan( text: word.substring(1, word.length), style: TextStyle(color: Colors.black, fontSize: 25)), ], ), ); } else if (word.endsWith(letter)) { richText = RichText( text: TextSpan( text: word.substring(0, word.length - 2), style: TextStyle(color: Colors.black, fontSize: 25), children: <TextSpan>[ TextSpan(text: letter, style: TextStyle(color: color, fontSize: 25)) ], ), ); } else { for (int i = 0; i < word.length; i++) { if (word[i] != letter && i < word.indexOf(letter)) { firstPart = firstPart + word[i]; } else if (word[i] != letter && i > word.indexOf(letter)) { remain = remain + word[i]; } } richText = RichText( text: TextSpan( text: firstPart, style: TextStyle(color: Colors.black, fontSize: 25), children: <TextSpan>[ TextSpan( text: letter, style: TextStyle(color: color, fontSize: 25), ), TextSpan( text: remain, style: TextStyle(color: Colors.black, fontSize: 25)), ], ), ); } return richText; } } the problem is inside the ontap method that when i found the position i can not convert it to index to get the real char that clicked if it is c chnage the color other wise show toast.
How get moving text in widget with given width
I am building a radio app. Like in Spotify, there is a bar with the current title and artist, the text should be in one line and in a given width. How can I let the text move from right to left and back? When using a self-made animation, I want to have a fixed speed of the moving text, so I need the time and the width of the text widget. Is there a package/built-in option to do this? Or do I have to use a self-made animation? If so, how can I get the text widget width? Controller and animation: AnimationController(duration: Duration(seconds: 10), vsync: this); animation = Tween<double>(begin: 0, end: 1) .animate(CurvedAnimation(parent: _controller, curve: Curves.linear)); animation.addListener(() { setState(() {}); }); _controller.repeat(); build method double value = -300 * (animation.value <= 0.5 ? animation.value : 1 - animation.value); return Container( child: SizedBox( width: widget.width, height: 24, child: Transform.translate( offset: Offset(value, 0), child: widget.text, ), ), );
You can do something like this: import 'dart:async'; import 'package:flutter/material.dart'; class ScrollingText extends StatefulWidget { final String text; final TextStyle textStyle; final Axis scrollAxis; final double ratioOfBlankToScreen; ScrollingText({ #required this.text, this.textStyle, this.scrollAxis: Axis.horizontal, this.ratioOfBlankToScreen: 0.25, }) : assert(text != null,); #override State<StatefulWidget> createState() { return ScrollingTextState(); } } class ScrollingTextState extends State<ScrollingText> with SingleTickerProviderStateMixin { ScrollController scrollController; double screenWidth; double screenHeight; double position = 0.0; Timer timer; final double _moveDistance = 3.0; final int _timerRest = 100; GlobalKey _key = GlobalKey(); #override void initState() { super.initState(); scrollController = ScrollController(); WidgetsBinding.instance.addPostFrameCallback((callback) { startTimer(); }); } void startTimer() { if (_key.currentContext != null) { double widgetWidth = _key.currentContext.findRenderObject().paintBounds.size.width; double widgetHeight = _key.currentContext.findRenderObject().paintBounds.size.height; timer = Timer.periodic(Duration(milliseconds: _timerRest), (timer) { double maxScrollExtent = scrollController.position.maxScrollExtent; double pixels = scrollController.position.pixels; if (pixels + _moveDistance >= maxScrollExtent) { if (widget.scrollAxis == Axis.horizontal) { position = (maxScrollExtent - screenWidth * widget.ratioOfBlankToScreen + widgetWidth) / 2 - widgetWidth + pixels - maxScrollExtent; } else { position = (maxScrollExtent - screenHeight * widget.ratioOfBlankToScreen + widgetHeight) / 2 - widgetHeight + pixels - maxScrollExtent; } scrollController.jumpTo(position); } position += _moveDistance; scrollController.animateTo(position, duration: Duration(milliseconds: _timerRest), curve: Curves.linear); }); } } #override void didChangeDependencies() { super.didChangeDependencies(); screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; } Widget getBothEndsChild() { if (widget.scrollAxis == Axis.vertical) { String newString = widget.text.split("").join("\n"); return Center( child: Text( newString, style: widget.textStyle, textAlign: TextAlign.center, ), ); } return Center( child: Text( widget.text, style: widget.textStyle, )); } Widget getCenterChild() { if (widget.scrollAxis == Axis.horizontal) { return Container(width: screenWidth * widget.ratioOfBlankToScreen); } else { return Container(height: screenHeight * widget.ratioOfBlankToScreen); } } #override void dispose() { super.dispose(); if (timer != null) { timer.cancel(); } } #override Widget build(BuildContext context) { return ListView( key: _key, scrollDirection: widget.scrollAxis, controller: scrollController, physics: NeverScrollableScrollPhysics(), children: <Widget>[ getBothEndsChild(), getCenterChild(), getBothEndsChild(), ], ); } } And use the widget like this: ScrollingText( text: text, textStyle: TextStyle(fontSize: 12), )
use this widget from pub.dev marquee. Marquee( text: 'There once was a boy who told this story about a boy: "', )