From 1915a051100de183ebce0101d2270d2b769cbc31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20G=C3=B6cer?= Date: Fri, 5 Sep 2025 05:09:04 +0200 Subject: [PATCH 1/2] Implement AutoSizeGroup --- lib/flutter_auto_size_text.dart | 23 +++- lib/src/auto_size_builder/auto_size.dart | 12 +- .../auto_size_builder/auto_size_builder.dart | 55 ++++++-- .../auto_size_builder/auto_size_element.dart | 2 +- .../auto_size_builder/render_auto_size.dart | 10 +- lib/src/auto_size_group.dart | 65 ++++++++++ lib/src/auto_size_group_builder.dart | 22 ++++ lib/src/auto_size_text.dart | 16 +-- lib/src/text_fitter.dart | 20 ++- test/group_builder_test.dart | 103 +++++++++++++++ test/group_test.dart | 121 ++++++++++++++++++ 11 files changed, 416 insertions(+), 33 deletions(-) create mode 100644 lib/src/auto_size_group.dart create mode 100644 lib/src/auto_size_group_builder.dart create mode 100644 test/group_builder_test.dart create mode 100644 test/group_test.dart diff --git a/lib/flutter_auto_size_text.dart b/lib/flutter_auto_size_text.dart index bf429ce..591e4e8 100644 --- a/lib/flutter_auto_size_text.dart +++ b/lib/flutter_auto_size_text.dart @@ -1 +1,22 @@ -export 'src/auto_size_text.dart'; +library; + +import 'dart:async'; +import 'dart:math'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; + +part 'src/auto_size_group.dart'; + +part 'src/auto_size_group_builder.dart'; + +part 'src/auto_size_text.dart'; + +part 'src/text_fitter.dart'; + +part 'src/auto_size_builder/auto_size.dart'; + +part 'src/auto_size_builder/auto_size_builder.dart'; + +part 'src/auto_size_builder/auto_size_element.dart'; + +part 'src/auto_size_builder/render_auto_size.dart'; diff --git a/lib/src/auto_size_builder/auto_size.dart b/lib/src/auto_size_builder/auto_size.dart index 08cd144..2579d3a 100644 --- a/lib/src/auto_size_builder/auto_size.dart +++ b/lib/src/auto_size_builder/auto_size.dart @@ -1,4 +1,4 @@ -part of 'auto_size_builder.dart'; +part of '../../flutter_auto_size_text.dart'; class _AutoSize extends RenderObjectWidget { _AutoSize({ @@ -19,11 +19,13 @@ class _AutoSize extends RenderObjectWidget { required this.maxFontSize, required this.stepGranularity, required this.presetFontSizes, + this.groupConstraints, + this.onConstraintsChanged, }) { _validateProperties(); } - final AutoSizeTextBuilder builder; + final _AutoSizeTextBuilder builder; final Widget? overflowReplacement; final TextSpan text; @@ -41,6 +43,8 @@ class _AutoSize extends RenderObjectWidget { final double maxFontSize; final double stepGranularity; final List? presetFontSizes; + final BoxConstraints? groupConstraints; + final void Function(BoxConstraints)? onConstraintsChanged; TextFitter _buildFitter() { return TextFitter( @@ -59,12 +63,14 @@ class _AutoSize extends RenderObjectWidget { maxFontSize: maxFontSize, stepGranularity: stepGranularity, presetFontSizes: presetFontSizes, + groupConstraints: groupConstraints, ); } @override _RenderAutoSize createRenderObject(BuildContext context) { - return _RenderAutoSize(fitter: _buildFitter()); + return _RenderAutoSize( + fitter: _buildFitter(), onConstraintsChanged: onConstraintsChanged); } @override diff --git a/lib/src/auto_size_builder/auto_size_builder.dart b/lib/src/auto_size_builder/auto_size_builder.dart index 6ccdf56..729c5e3 100644 --- a/lib/src/auto_size_builder/auto_size_builder.dart +++ b/lib/src/auto_size_builder/auto_size_builder.dart @@ -1,16 +1,10 @@ -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:flutter_auto_size_text/src/text_fitter.dart'; +part of '../../flutter_auto_size_text.dart'; -part 'auto_size.dart'; -part 'auto_size_element.dart'; -part 'render_auto_size.dart'; - -typedef AutoSizeTextBuilder = Widget Function( +typedef _AutoSizeTextBuilder = Widget Function( BuildContext context, double textScaleFactor, bool overflow); -class AutoSizeBuilder extends StatefulWidget { - const AutoSizeBuilder({ +class _AutoSizeBuilder extends StatefulWidget { + const _AutoSizeBuilder({ super.key, required this.builder, this.overflowReplacement, @@ -30,9 +24,10 @@ class AutoSizeBuilder extends StatefulWidget { this.maxFontSize, this.stepGranularity, this.presetFontSizes, + this.group, }); - final AutoSizeTextBuilder builder; + final _AutoSizeTextBuilder builder; /// {@macro auto_size_text.overflowReplacement} final Widget? overflowReplacement; @@ -83,11 +78,29 @@ class AutoSizeBuilder extends StatefulWidget { /// {@macro auto_size_text.presetFontSizes} final List? presetFontSizes; + /// {@macro auto_size_text.group} + final AutoSizeGroup? group; + @override - State createState() => _AutoSizeBuilderState(); + State<_AutoSizeBuilder> createState() => _AutoSizeBuilderState(); } -class _AutoSizeBuilderState extends State { +class _AutoSizeBuilderState extends State<_AutoSizeBuilder> { + @override + void initState() { + super.initState(); + widget.group?._register(this); + } + + @override + void didUpdateWidget(covariant _AutoSizeBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.group != widget.group) { + oldWidget.group?._remove(this); + widget.group?._register(this); + } + } + @override Widget build(BuildContext context) { final defaultTextStyle = DefaultTextStyle.of(context); @@ -129,6 +142,22 @@ class _AutoSizeBuilderState extends State { maxFontSize: widget.maxFontSize ?? double.infinity, stepGranularity: widget.stepGranularity ?? 1.0, presetFontSizes: widget.presetFontSizes, + groupConstraints: widget.group?._effectiveConstraints, + onConstraintsChanged: widget.group != null + ? (newConstraints) { + widget.group?._updateConstraints(this, newConstraints); + } + : null, ); } + + void _notifySync() { + setState(() {}); + } + + @override + void dispose() { + widget.group?._remove(this); + super.dispose(); + } } diff --git a/lib/src/auto_size_builder/auto_size_element.dart b/lib/src/auto_size_builder/auto_size_element.dart index e5e9868..453c4e1 100644 --- a/lib/src/auto_size_builder/auto_size_element.dart +++ b/lib/src/auto_size_builder/auto_size_element.dart @@ -1,4 +1,4 @@ -part of 'auto_size_builder.dart'; +part of '../../flutter_auto_size_text.dart'; class _AutoSizeElement extends RenderObjectElement { _AutoSizeElement(_AutoSize super.widget); diff --git a/lib/src/auto_size_builder/render_auto_size.dart b/lib/src/auto_size_builder/render_auto_size.dart index 6c72190..9aab5ca 100644 --- a/lib/src/auto_size_builder/render_auto_size.dart +++ b/lib/src/auto_size_builder/render_auto_size.dart @@ -1,4 +1,4 @@ -part of 'auto_size_builder.dart'; +part of '../../flutter_auto_size_text.dart'; class _AutoSizeParentData extends ParentData with ContainerParentDataMixin {} @@ -7,7 +7,8 @@ class _RenderAutoSize extends RenderBox with ContainerRenderObjectMixin> { - _RenderAutoSize({required TextFitter fitter}) : _fitter = fitter; + _RenderAutoSize({required TextFitter fitter, this.onConstraintsChanged}) + : _fitter = fitter; var _overflow = false; var _needsBuild = true; @@ -16,6 +17,8 @@ class _RenderAutoSize extends RenderBox double? _longestWordWidth; Function(double, bool)? _buildCallback; + final void Function(BoxConstraints)? onConstraintsChanged; + void updateBuildCallback(Function(double, bool)? buildCallback) { if (_buildCallback == buildCallback) return; _previousTextScaleFactor = null; @@ -24,6 +27,7 @@ class _RenderAutoSize extends RenderBox } TextFitter _fitter; + void updateTextFitter(TextFitter fitter) { if (_fitter == fitter) return; if (_fitter.text != fitter.text) { @@ -106,6 +110,8 @@ class _RenderAutoSize extends RenderBox void performLayout() { final constraints = this.constraints; + onConstraintsChanged?.call(constraints); + final result = _fitter.fit(constraints, _longestWordWidth); _longestWordWidth = result.longestWordWidth; diff --git a/lib/src/auto_size_group.dart b/lib/src/auto_size_group.dart new file mode 100644 index 0000000..c480146 --- /dev/null +++ b/lib/src/auto_size_group.dart @@ -0,0 +1,65 @@ +part of '../flutter_auto_size_text.dart'; + +/// Controller to synchronize the fontSize of multiple AutoSizeTexts. +class AutoSizeGroup { + final _listeners = <_AutoSizeBuilderState, BoxConstraints>{}; + var _widgetsNotified = false; + + void _register(_AutoSizeBuilderState text) { + _listeners[text] = const BoxConstraints(); + } + + BoxConstraints get _effectiveConstraints { + const constraints = BoxConstraints(); + double maxWidth = constraints.maxWidth; + double maxHeight = constraints.maxHeight; + final groupConstraints = _listeners.values.nonNulls.toList(); + + for (final groupConstraint in groupConstraints) { + if (groupConstraint.maxWidth < maxWidth) { + maxWidth = groupConstraint.maxWidth; + } + if (groupConstraint.maxHeight < maxHeight) { + maxHeight = groupConstraint.maxHeight; + } + } + + final effectiveConstraints = BoxConstraints( + maxWidth: maxWidth, + maxHeight: maxHeight, + ); + + return effectiveConstraints; + } + + void _updateConstraints( + _AutoSizeBuilderState text, BoxConstraints constraints) { + final oldEffectiveConstraints = _effectiveConstraints; + _listeners[text] = constraints; + final newEffectiveConstraints = _effectiveConstraints; + + if (oldEffectiveConstraints != newEffectiveConstraints) { + _widgetsNotified = false; + scheduleMicrotask(_notifyListeners); + } + } + + void _notifyListeners() { + if (_widgetsNotified) { + return; + } else { + _widgetsNotified = true; + } + + for (final textState in _listeners.keys) { + if (textState.mounted) { + textState._notifySync(); + } + } + } + + void _remove(_AutoSizeBuilderState text) { + _updateConstraints(text, const BoxConstraints()); + _listeners.remove(text); + } +} diff --git a/lib/src/auto_size_group_builder.dart b/lib/src/auto_size_group_builder.dart new file mode 100644 index 0000000..49d274a --- /dev/null +++ b/lib/src/auto_size_group_builder.dart @@ -0,0 +1,22 @@ +part of '../flutter_auto_size_text.dart'; + +/// A Flutter widget that provides an [AutoSizeGroup] to its builder function. +class AutoSizeGroupBuilder extends StatefulWidget { + final Widget Function(BuildContext context, AutoSizeGroup autoSizeGroup) + builder; + + /// Creates an [AutoSizeGroupBuilder] widget. + const AutoSizeGroupBuilder({super.key, required this.builder}); + + @override + _AutoSizeGroupBuilderState createState() => _AutoSizeGroupBuilderState(); +} + +class _AutoSizeGroupBuilderState extends State { + final _group = AutoSizeGroup(); + + @override + Widget build(BuildContext context) { + return widget.builder(context, _group); + } +} diff --git a/lib/src/auto_size_text.dart b/lib/src/auto_size_text.dart index f98263e..46a21b6 100644 --- a/lib/src/auto_size_text.dart +++ b/lib/src/auto_size_text.dart @@ -1,5 +1,4 @@ -import 'package:flutter/widgets.dart'; -import 'package:flutter_auto_size_text/src/auto_size_builder/auto_size_builder.dart'; +part of '../flutter_auto_size_text.dart'; /// Flutter widget that automatically resizes text to fit perfectly within its /// bounds. @@ -140,7 +139,7 @@ class AutoSizeText extends StatelessWidget { /// If you want multiple [AutoSizeText]s to have the same text size, give all /// of them the same [AutoSizeGroup] instance. All of them will have the /// size of the smallest [AutoSizeText] - //final AutoSizeGroup? group; + final AutoSizeGroup? group; /// {@template auto_size_text.wrapWords} /// Whether words which don't fit in one line should be wrapped. @@ -185,11 +184,11 @@ class AutoSizeText extends StatelessWidget { this.maxFontSize, this.stepGranularity, this.presetFontSizes, - //this.group, + this.group, this.wrapWords, this.overflowReplacement, this.overflowCallback, - }) : textSpan = null; + }) : textSpan = null; /// Creates a [AutoSizeText] widget with a [TextSpan]. const AutoSizeText.rich( @@ -212,16 +211,16 @@ class AutoSizeText extends StatelessWidget { this.maxFontSize, this.stepGranularity, this.presetFontSizes, - //this.group, + this.group, this.wrapWords, this.overflowReplacement, this.overflowCallback, - }) : data = null; + }) : data = null; @override Widget build(BuildContext context) { final span = textSpan ?? TextSpan(text: data); - return AutoSizeBuilder( + return _AutoSizeBuilder( text: span, style: style, builder: (context, scale, overflow) { @@ -246,6 +245,7 @@ class AutoSizeText extends StatelessWidget { maxFontSize: maxFontSize, stepGranularity: stepGranularity, presetFontSizes: presetFontSizes, + group: group, textAlign: textAlign, textDirection: textDirection, locale: locale, diff --git a/lib/src/text_fitter.dart b/lib/src/text_fitter.dart index 31339df..779a6ae 100644 --- a/lib/src/text_fitter.dart +++ b/lib/src/text_fitter.dart @@ -1,6 +1,4 @@ -import 'dart:math'; - -import 'package:flutter/widgets.dart'; +part of '../flutter_auto_size_text.dart'; const _kDefaultFontSize = 14; @@ -21,6 +19,7 @@ class TextFitter { required this.maxFontSize, this.stepGranularity = 1.0, this.presetFontSizes, + this.groupConstraints, }); final TextSpan text; @@ -38,10 +37,19 @@ class TextFitter { final double maxFontSize; final double stepGranularity; final List? presetFontSizes; + final BoxConstraints? groupConstraints; double? _longestWordWidth; TextFitResult fit(BoxConstraints constraints, [double? longestWordWidth]) { + var effectiveConstraints = constraints; + if (groupConstraints != null) { + effectiveConstraints = BoxConstraints( + maxWidth: min(constraints.maxWidth, groupConstraints!.maxWidth), + maxHeight: min(constraints.maxHeight, groupConstraints!.maxHeight), + ); + } + int left; int right; @@ -54,7 +62,7 @@ class TextFitter { if (presetFontSizes == null) { final defaultFontSize = fontSize.clamp(minFontSize, maxFontSize); final defaultScale = defaultFontSize * textScaleFactor / fontSize; - final result = _measureText(defaultScale, constraints); + final result = _measureText(defaultScale, effectiveConstraints); if (!result.overflow) { return result; } @@ -75,7 +83,7 @@ class TextFitter { } else { scale = presetFontSizes[mid] * textScaleFactor / fontSize; } - final result = _measureText(scale, constraints); + final result = _measureText(scale, effectiveConstraints); if (result.overflow) { right = mid - 1; } else { @@ -205,6 +213,7 @@ class TextFitter { other.minFontSize == minFontSize && other.maxFontSize == maxFontSize && other.stepGranularity == stepGranularity && + other.groupConstraints == groupConstraints && other.presetFontSizes == presetFontSizes; } @@ -224,6 +233,7 @@ class TextFitter { minFontSize, maxFontSize, stepGranularity, + groupConstraints, presetFontSizes, ); } diff --git a/test/group_builder_test.dart b/test/group_builder_test.dart new file mode 100644 index 0000000..b391438 --- /dev/null +++ b/test/group_builder_test.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_auto_size_text/flutter_auto_size_text.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +Widget testWidget({required double width1, required double width2}) { + return MaterialApp( + home: AutoSizeGroupBuilder( + builder: (_, group) => Column( + children: [ + SizedBox( + width: width1, + height: 100, + child: AutoSizeText( + 'XXXXXX', + style: const TextStyle(fontSize: 60), + minFontSize: 1, + maxLines: 1, + group: group, + ), + ), + SizedBox( + width: width2, + height: 100.0, + child: AutoSizeText( + 'XXXXXX', + style: const TextStyle(fontSize: 60), + minFontSize: 1, + maxLines: 1, + group: group, + ), + ), + ], + ), + ), + ); +} + +void _expectFontSizes(WidgetTester tester, double fontSize) { + final texts = tester.widgetList(find.byType(RichText)); + for (final text in texts) { + expect(effectiveFontSize(text as RichText), fontSize); + } +} + +void main() { + testWidgets('Group sync', (tester) async { + await tester.pumpWidget(testWidget(width1: 300, width2: 300)); + + _expectFontSizes(tester, 50); + + await tester.pumpWidget(testWidget(width1: 200, width2: 300)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 33); + + await tester.pumpWidget(testWidget(width1: 200, width2: 150)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 25); + + await tester.pumpWidget(testWidget(width1: 200, width2: 100)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 16); + + await tester.pumpWidget(testWidget(width1: 60, width2: 60)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 10); + + await tester.pumpWidget(testWidget(width1: 200, width2: 60)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 10); + + await tester.pumpWidget(testWidget(width1: 200, width2: 250)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 33); + + await tester.pumpWidget(testWidget(width1: 250, width2: 250)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + + _expectFontSizes(tester, 41); + + await tester.pumpWidget(testWidget(width1: 300, width2: 300)); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + // Upsizing both requires an extra frame to settle on the new size. + await tester.pump(); + + _expectFontSizes(tester, 50); + }); +} diff --git a/test/group_test.dart b/test/group_test.dart new file mode 100644 index 0000000..0a158ea --- /dev/null +++ b/test/group_test.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_auto_size_text/flutter_auto_size_text.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +class GroupTest extends StatefulWidget { + @override + GroupTestState createState() => GroupTestState(); +} + +class GroupTestState extends State { + AutoSizeGroup group = AutoSizeGroup(); + double width1 = 300.0; + double width2 = 300.0; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Column( + children: [ + SizedBox( + width: width1, + height: 100, + child: AutoSizeText( + 'XXXXXX', + style: const TextStyle(fontSize: 60), + minFontSize: 1, + maxLines: 1, + group: group, + ), + ), + SizedBox( + width: width2, + height: 100.0, + child: AutoSizeText( + 'XXXXXX', + style: const TextStyle(fontSize: 60), + minFontSize: 1, + maxLines: 1, + group: group, + ), + ), + ], + ), + ); + } + + void refresh() { + setState(() {}); + } +} + +void _expectFontSizes(WidgetTester tester, double fontSize) { + final texts = tester.widgetList(find.byType(RichText)); + for (final text in texts) { + expect(effectiveFontSize(text as RichText), fontSize); + } +} + +void main() { + testWidgets('Group sync', (tester) async { + await tester.pumpWidget(GroupTest()); + + _expectFontSizes(tester, 50); + + final state = tester.state(find.byType(GroupTest)) as GroupTestState; + + state.width1 = 200; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 33); + + state.width2 = 150; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 25); + + state.width2 = 100; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 16); + + state.width1 = 60; + state.width2 = 60; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 10); + + state.width1 = 200; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 10); + + state.width2 = 250; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 33); + + state.width1 = 250; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 41); + + state.width1 = 300; + state.width2 = 300; + state.refresh(); + await tester.pump(Duration.zero); + await tester.pump(Duration.zero); + _expectFontSizes(tester, 50); + + await tester.pump(Duration.zero); + }); +} From d1af006cd56ff878a56b4c474ef4cdfb2b7015bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20G=C3=B6cer?= Date: Fri, 5 Sep 2025 07:08:58 +0200 Subject: [PATCH 2/2] Use max font size instead of layout constraints --- lib/src/auto_size_builder/auto_size.dart | 14 +++--- .../auto_size_builder/auto_size_builder.dart | 12 ++--- .../auto_size_builder/auto_size_element.dart | 4 +- .../auto_size_builder/render_auto_size.dart | 27 ++++++++---- lib/src/auto_size_group.dart | 44 ++++++------------- lib/src/auto_size_text.dart | 11 ++++- lib/src/text_fitter.dart | 16 +------ 7 files changed, 60 insertions(+), 68 deletions(-) diff --git a/lib/src/auto_size_builder/auto_size.dart b/lib/src/auto_size_builder/auto_size.dart index 2579d3a..6273e5b 100644 --- a/lib/src/auto_size_builder/auto_size.dart +++ b/lib/src/auto_size_builder/auto_size.dart @@ -17,10 +17,10 @@ class _AutoSize extends RenderObjectWidget { required this.textScaleFactor, required this.minFontSize, required this.maxFontSize, + this.groupMaxFontSize, required this.stepGranularity, required this.presetFontSizes, - this.groupConstraints, - this.onConstraintsChanged, + this.onMaxPossibleFontSizeChanged, }) { _validateProperties(); } @@ -43,8 +43,8 @@ class _AutoSize extends RenderObjectWidget { final double maxFontSize; final double stepGranularity; final List? presetFontSizes; - final BoxConstraints? groupConstraints; - final void Function(BoxConstraints)? onConstraintsChanged; + final double? groupMaxFontSize; + final void Function(double)? onMaxPossibleFontSizeChanged; TextFitter _buildFitter() { return TextFitter( @@ -63,20 +63,22 @@ class _AutoSize extends RenderObjectWidget { maxFontSize: maxFontSize, stepGranularity: stepGranularity, presetFontSizes: presetFontSizes, - groupConstraints: groupConstraints, ); } @override _RenderAutoSize createRenderObject(BuildContext context) { return _RenderAutoSize( - fitter: _buildFitter(), onConstraintsChanged: onConstraintsChanged); + fitter: _buildFitter(), + onMaxPossibleFontSizeChanged: onMaxPossibleFontSizeChanged, + groupMaxFontSize: groupMaxFontSize); } @override void updateRenderObject( BuildContext context, covariant _RenderAutoSize renderObject) { renderObject.updateTextFitter(_buildFitter()); + renderObject.updateGroupMaxFontSize(groupMaxFontSize); } @override diff --git a/lib/src/auto_size_builder/auto_size_builder.dart b/lib/src/auto_size_builder/auto_size_builder.dart index 729c5e3..eb605ee 100644 --- a/lib/src/auto_size_builder/auto_size_builder.dart +++ b/lib/src/auto_size_builder/auto_size_builder.dart @@ -1,7 +1,7 @@ part of '../../flutter_auto_size_text.dart'; -typedef _AutoSizeTextBuilder = Widget Function( - BuildContext context, double textScaleFactor, bool overflow); +typedef _AutoSizeTextBuilder = Widget Function(BuildContext context, + double textScaleFactor, double? groupMaxFontSize, bool overflow); class _AutoSizeBuilder extends StatefulWidget { const _AutoSizeBuilder({ @@ -140,12 +140,12 @@ class _AutoSizeBuilderState extends State<_AutoSizeBuilder> { textScaleFactor: widget.textScaleFactor ?? 1, minFontSize: widget.minFontSize ?? 12.0, maxFontSize: widget.maxFontSize ?? double.infinity, + groupMaxFontSize: widget.group?._effectiveMaxPossibleFontSize, stepGranularity: widget.stepGranularity ?? 1.0, presetFontSizes: widget.presetFontSizes, - groupConstraints: widget.group?._effectiveConstraints, - onConstraintsChanged: widget.group != null - ? (newConstraints) { - widget.group?._updateConstraints(this, newConstraints); + onMaxPossibleFontSizeChanged: widget.group != null + ? (maxPossibleFontSize) { + widget.group?._updateFontSize(this, maxPossibleFontSize); } : null, ); diff --git a/lib/src/auto_size_builder/auto_size_element.dart b/lib/src/auto_size_builder/auto_size_element.dart index 453c4e1..e7b9145 100644 --- a/lib/src/auto_size_builder/auto_size_element.dart +++ b/lib/src/auto_size_builder/auto_size_element.dart @@ -40,12 +40,12 @@ class _AutoSizeElement extends RenderObjectElement { super.unmount(); } - void _updateText(double textScaleFactor, bool overflow) { + void _updateText(double textScaleFactor, double? groupMaxFontSize, bool overflow) { owner!.buildScope(this, () { _overflow = overflow; Widget built; try { - built = widget.builder(this, textScaleFactor, overflow); + built = widget.builder(this, textScaleFactor, groupMaxFontSize, overflow); debugWidgetBuilderValue(widget, built); } catch (e) { built = ErrorWidget(e); diff --git a/lib/src/auto_size_builder/render_auto_size.dart b/lib/src/auto_size_builder/render_auto_size.dart index 9aab5ca..1f6f087 100644 --- a/lib/src/auto_size_builder/render_auto_size.dart +++ b/lib/src/auto_size_builder/render_auto_size.dart @@ -7,7 +7,10 @@ class _RenderAutoSize extends RenderBox with ContainerRenderObjectMixin> { - _RenderAutoSize({required TextFitter fitter, this.onConstraintsChanged}) + _RenderAutoSize( + {required TextFitter fitter, + this.onMaxPossibleFontSizeChanged, + required this.groupMaxFontSize}) : _fitter = fitter; var _overflow = false; @@ -16,10 +19,11 @@ class _RenderAutoSize extends RenderBox bool? _previousOverflow; double? _longestWordWidth; - Function(double, bool)? _buildCallback; - final void Function(BoxConstraints)? onConstraintsChanged; + Function(double, double?, bool)? _buildCallback; + double? groupMaxFontSize; + final void Function(double)? onMaxPossibleFontSizeChanged; - void updateBuildCallback(Function(double, bool)? buildCallback) { + void updateBuildCallback(Function(double, double?, bool)? buildCallback) { if (_buildCallback == buildCallback) return; _previousTextScaleFactor = null; _buildCallback = buildCallback; @@ -38,6 +42,12 @@ class _RenderAutoSize extends RenderBox markNeedsLayout(); } + void updateGroupMaxFontSize(double? newGroupMaxFontSize) { + if (groupMaxFontSize == newGroupMaxFontSize) return; + groupMaxFontSize = newGroupMaxFontSize; + markNeedsLayout(); + } + RenderBox get child => _overflow ? lastChild! : firstChild!; bool get hasReplacement => !identical(firstChild, lastChild); @@ -110,8 +120,6 @@ class _RenderAutoSize extends RenderBox void performLayout() { final constraints = this.constraints; - onConstraintsChanged?.call(constraints); - final result = _fitter.fit(constraints, _longestWordWidth); _longestWordWidth = result.longestWordWidth; @@ -121,8 +129,11 @@ class _RenderAutoSize extends RenderBox _previousTextScaleFactor = result.scale; _previousOverflow = result.overflow; _needsBuild = false; - invokeLayoutCallback( - (_) => _buildCallback!(result.scale, result.overflow)); + final fontSize = _fitter.text.style?.fontSize ?? _kDefaultFontSize; + final maxPossibleFontSize = fontSize * result.scale; + onMaxPossibleFontSizeChanged?.call(maxPossibleFontSize); + invokeLayoutCallback((_) => + _buildCallback!(result.scale, groupMaxFontSize, result.overflow)); } _overflow = result.overflow; diff --git a/lib/src/auto_size_group.dart b/lib/src/auto_size_group.dart index c480146..8f286c8 100644 --- a/lib/src/auto_size_group.dart +++ b/lib/src/auto_size_group.dart @@ -2,43 +2,27 @@ part of '../flutter_auto_size_text.dart'; /// Controller to synchronize the fontSize of multiple AutoSizeTexts. class AutoSizeGroup { - final _listeners = <_AutoSizeBuilderState, BoxConstraints>{}; + final _listeners = <_AutoSizeBuilderState, double>{}; var _widgetsNotified = false; void _register(_AutoSizeBuilderState text) { - _listeners[text] = const BoxConstraints(); + _listeners[text] = double.infinity; } - BoxConstraints get _effectiveConstraints { - const constraints = BoxConstraints(); - double maxWidth = constraints.maxWidth; - double maxHeight = constraints.maxHeight; - final groupConstraints = _listeners.values.nonNulls.toList(); - - for (final groupConstraint in groupConstraints) { - if (groupConstraint.maxWidth < maxWidth) { - maxWidth = groupConstraint.maxWidth; - } - if (groupConstraint.maxHeight < maxHeight) { - maxHeight = groupConstraint.maxHeight; - } - } - - final effectiveConstraints = BoxConstraints( - maxWidth: maxWidth, - maxHeight: maxHeight, - ); - - return effectiveConstraints; + double get _effectiveMaxPossibleFontSize { + final minMaxPossibleFontSize = _listeners.values.fold( + double.infinity, + (previousValue, element) => + element < previousValue ? element : previousValue); + return minMaxPossibleFontSize; } - void _updateConstraints( - _AutoSizeBuilderState text, BoxConstraints constraints) { - final oldEffectiveConstraints = _effectiveConstraints; - _listeners[text] = constraints; - final newEffectiveConstraints = _effectiveConstraints; + void _updateFontSize(_AutoSizeBuilderState text, double maxPossibleFontSize) { + final oldEffectiveMaxPossibleFontSize = _effectiveMaxPossibleFontSize; + _listeners[text] = maxPossibleFontSize; + final newEffectiveMaxPossibleFontSize = _effectiveMaxPossibleFontSize; - if (oldEffectiveConstraints != newEffectiveConstraints) { + if (oldEffectiveMaxPossibleFontSize != newEffectiveMaxPossibleFontSize) { _widgetsNotified = false; scheduleMicrotask(_notifyListeners); } @@ -59,7 +43,7 @@ class AutoSizeGroup { } void _remove(_AutoSizeBuilderState text) { - _updateConstraints(text, const BoxConstraints()); + _updateFontSize(text, double.infinity); _listeners.remove(text); } } diff --git a/lib/src/auto_size_text.dart b/lib/src/auto_size_text.dart index 46a21b6..de3443b 100644 --- a/lib/src/auto_size_text.dart +++ b/lib/src/auto_size_text.dart @@ -223,8 +223,15 @@ class AutoSizeText extends StatelessWidget { return _AutoSizeBuilder( text: span, style: style, - builder: (context, scale, overflow) { + builder: (context, scale, groupMaxFontSize, overflow) { overflowCallback?.call(overflow); + final fontSize = + span.style?.fontSize ?? style?.fontSize ?? _kDefaultFontSize; + final scaledFontSize = fontSize * scale; + final adjustedScale = + groupMaxFontSize != null && scaledFontSize > groupMaxFontSize + ? groupMaxFontSize / fontSize + : scale; return Text.rich( span, key: textKey, @@ -235,7 +242,7 @@ class AutoSizeText extends StatelessWidget { locale: locale, softWrap: softWrap, overflow: this.overflow, - textScaler: TextScaler.linear(scale), + textScaler: TextScaler.linear(adjustedScale), maxLines: maxLines, semanticsLabel: semanticsLabel, ); diff --git a/lib/src/text_fitter.dart b/lib/src/text_fitter.dart index 779a6ae..878e0ac 100644 --- a/lib/src/text_fitter.dart +++ b/lib/src/text_fitter.dart @@ -19,7 +19,6 @@ class TextFitter { required this.maxFontSize, this.stepGranularity = 1.0, this.presetFontSizes, - this.groupConstraints, }); final TextSpan text; @@ -37,19 +36,10 @@ class TextFitter { final double maxFontSize; final double stepGranularity; final List? presetFontSizes; - final BoxConstraints? groupConstraints; double? _longestWordWidth; TextFitResult fit(BoxConstraints constraints, [double? longestWordWidth]) { - var effectiveConstraints = constraints; - if (groupConstraints != null) { - effectiveConstraints = BoxConstraints( - maxWidth: min(constraints.maxWidth, groupConstraints!.maxWidth), - maxHeight: min(constraints.maxHeight, groupConstraints!.maxHeight), - ); - } - int left; int right; @@ -62,7 +52,7 @@ class TextFitter { if (presetFontSizes == null) { final defaultFontSize = fontSize.clamp(minFontSize, maxFontSize); final defaultScale = defaultFontSize * textScaleFactor / fontSize; - final result = _measureText(defaultScale, effectiveConstraints); + final result = _measureText(defaultScale, constraints); if (!result.overflow) { return result; } @@ -83,7 +73,7 @@ class TextFitter { } else { scale = presetFontSizes[mid] * textScaleFactor / fontSize; } - final result = _measureText(scale, effectiveConstraints); + final result = _measureText(scale, constraints); if (result.overflow) { right = mid - 1; } else { @@ -213,7 +203,6 @@ class TextFitter { other.minFontSize == minFontSize && other.maxFontSize == maxFontSize && other.stepGranularity == stepGranularity && - other.groupConstraints == groupConstraints && other.presetFontSizes == presetFontSizes; } @@ -233,7 +222,6 @@ class TextFitter { minFontSize, maxFontSize, stepGranularity, - groupConstraints, presetFontSizes, ); }