Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active September 3, 2024 11:43
Show Gist options
  • Save PlugFox/b0db4c6754c8cfcc0cec0369b58e086f to your computer and use it in GitHub Desktop.
Save PlugFox/b0db4c6754c8cfcc0cec0369b58e086f to your computer and use it in GitHub Desktop.
Text measure widget
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// {@template text_measure}
/// TextMeasure widget.
/// {@endtemplate}
class TextMeasure extends StatefulWidget {
/// {@macro text_measure}
const TextMeasure({
super.key,
required this.text,
required this.width,
this.style = const TextStyle(
fontSize: 12,
fontWeight: FontWeight.normal,
),
required this.builder,
this.onSizeChanged,
});
/// Text to measure
final String text;
/// Max width of text
final double width;
/// Text style
final TextStyle style;
/// Callback with size
final void Function(Size)? onSizeChanged;
/// Builder
final Widget Function(BuildContext context, Size size) builder;
@override
State<TextMeasure> createState() => _TextMeasureState();
}
/// State for widget TextMeasure.
class _TextMeasureState extends State<TextMeasure> {
Size textSize = Size.zero;
/* #region Lifecycle */
@override
void initState() {
super.initState();
rebuild(widget.text, widget.width, widget.style);
}
@override
void didUpdateWidget(covariant TextMeasure oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.text == widget.text &&
oldWidget.width == widget.width &&
oldWidget.style == widget.style) {
return;
}
rebuild(widget.text, widget.width, widget.style);
}
/* #endregion */
void rebuild(String text, double width, TextStyle style,
{bool async = false}) =>
runZonedGuarded<void>(
() async {
Size size;
if (async) {
// ignore: deprecated_member_use_from_same_package
size = await measureTextAsync(text, width, style);
} else {
size = measureText(text, width, style);
}
if (!mounted || textSize == size) return;
widget.onSizeChanged?.call(size);
setState(() => textSize = size);
},
(error, stackTrace) {
if (!mounted) return;
ScaffoldMessenger.maybeOf(context)?.showSnackBar(
SnackBar(
content: Text('$error'),
duration: const Duration(seconds: 5),
backgroundColor: Colors.red,
action: SnackBarAction(
label: 'Retry',
onPressed: () => rebuild(text, width, style, async: true),
),
),
);
setState(() => textSize = Size.zero);
},
);
@Deprecated('Does not work in isolate')
static FutureOr<Size> measureTextAsync(
String text, double width, TextStyle style) =>
compute(
_measureTextAsync$Compute,
(
text: text,
width: width,
style: style,
token: null /* RootIsolateToken.instance */,
),
);
@Deprecated('Does not work in isolate')
static FutureOr<Size> _measureTextAsync$Compute(
({
String text,
double width,
TextStyle style,
RootIsolateToken? token
}) args,
) {
if (args.token case RootIsolateToken token) {
// Initialize Flutter services and allow to use method channels
BackgroundIsolateBinaryMessenger.ensureInitialized(token);
}
return measureText(args.text, args.width, args.style);
}
/// Измерение текста без рендеринга
static Size measureText(String text, double width, TextStyle style) {
// Создаем объект TextSpan, который содержит текст и его стиль
final textSpan = TextSpan(text: text, style: style);
// Создаем объект TextPainter, который может выполнять измерение текста без его реального рендеринга
final textPainter = TextPainter(
text: textSpan,
// это важно, так как текст должен иметь направление для корректного измерения
textDirection: TextDirection.ltr,
// опционально, устанавливает максимальное количество строк
//maxLines: 1,
)
// Вызываем layout(), чтобы TextPainter мог произвести расчеты
..layout(maxWidth: width);
// Теперь мы можем получить размеры текста
return textPainter.size;
}
@override
Widget build(BuildContext context) => widget.builder(context, textSize);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment