Last active
September 3, 2024 11:43
-
-
Save PlugFox/b0db4c6754c8cfcc0cec0369b58e086f to your computer and use it in GitHub Desktop.
Text measure widget
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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