Skip to content

Commit

Permalink
Remove textScaleFactor dependent logic from AppBar (#128112)
Browse files Browse the repository at this point in the history
I am trying to remove `textScaleFactor`-dependent logic from the framework since it's likely going to be deprecated, hopefully the original logic isn't from the material spec.

I stole the sample code from #125038 and here are the screenshots (`textScaleFactor = 3.0`). 

Internal Tests: **no relevant test failures**

|  Medium  |   Large |
| --------------- | --------------- |
| ![flutter_01](https://github.com/flutter/flutter/assets/31859944/515226d9-716f-470a-b794-5fd14b957d36) | ![flutter_03](https://github.com/flutter/flutter/assets/31859944/475c421d-550e-4d02-be08-709b63b63011) |
  • Loading branch information
LongCatIsLooong authored Jun 6, 2023
1 parent 257c4ee commit 73e1f23
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 120 deletions.
4 changes: 2 additions & 2 deletions dev/tools/gen_defaults/lib/app_bar_template.dart
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
${textStyle('md.comp.top-app-bar.medium.headline')}?.apply(color: ${color('md.comp.top-app-bar.medium.headline.color')});
@override
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
}
class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
Expand All @@ -102,7 +102,7 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
${textStyle('md.comp.top-app-bar.large.headline')}?.apply(color: ${color('md.comp.top-app-bar.large.headline.color')});
@override
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
}
''';
}
257 changes: 178 additions & 79 deletions packages/flutter/lib/src/material/app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import 'theme.dart';
// late String _logoAsset;
// double _myToolbarHeight = 250.0;

typedef _FlexibleConfigBuilder = _ScrollUnderFlexibleConfig Function(BuildContext);

const double _kLeadingWidth = kToolbarHeight; // So the leading button is square.
const double _kMaxTitleTextScaleFactor = 1.34; // TODO(perc): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769.

Expand Down Expand Up @@ -1259,17 +1261,15 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
final double toolbarOpacity = !pinned || isPinnedWithOpacityFade
? clampDouble(visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight), 0.0, 1.0)
: 1.0;
final Widget? effectiveTitle;
if (variant == _SliverAppVariant.small) {
effectiveTitle = title;
} else {
effectiveTitle = AnimatedOpacity(
final Widget? effectiveTitle = switch (variant) {
_SliverAppVariant.small => title,
_SliverAppVariant.medium || _SliverAppVariant.large => AnimatedOpacity(
opacity: isScrolledUnder ? 1 : 0,
duration: const Duration(milliseconds: 500),
curve: const Cubic(0.2, 0.0, 0.0, 1.0),
child: title,
);
}
),
};

final Widget appBar = FlexibleSpaceBar.createSettings(
minExtent: minExtent,
Expand Down Expand Up @@ -1971,8 +1971,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
effectiveFlexibleSpace = widget.flexibleSpace ?? _ScrollUnderFlexibleSpace(
title: widget.title,
foregroundColor: widget.foregroundColor,
variant: _ScrollUnderFlexibleVariant.medium,
primary: widget.primary,
configBuilder: _MediumScrollUnderFlexibleConfig.new,
titleTextStyle: widget.titleTextStyle,
bottomHeight: bottomHeight,
);
Expand All @@ -1984,8 +1983,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix
effectiveFlexibleSpace = widget.flexibleSpace ?? _ScrollUnderFlexibleSpace(
title: widget.title,
foregroundColor: widget.foregroundColor,
variant: _ScrollUnderFlexibleVariant.large,
primary: widget.primary,
configBuilder: _LargeScrollUnderFlexibleConfig.new,
titleTextStyle: widget.titleTextStyle,
bottomHeight: bottomHeight,
);
Expand Down Expand Up @@ -2081,79 +2079,48 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox {
}
}

enum _ScrollUnderFlexibleVariant { medium, large }

class _ScrollUnderFlexibleSpace extends StatelessWidget {
const _ScrollUnderFlexibleSpace({
this.title,
this.foregroundColor,
required this.variant,
this.primary = true,
required this.configBuilder,
this.titleTextStyle,
required this.bottomHeight,
});

final Widget? title;
final Color? foregroundColor;
final _ScrollUnderFlexibleVariant variant;
final bool primary;
final _FlexibleConfigBuilder configBuilder;
final TextStyle? titleTextStyle;
final double bottomHeight;

@override
Widget build(BuildContext context) {
late final ThemeData theme = Theme.of(context);
late final AppBarTheme appBarTheme = AppBarTheme.of(context);
final AppBarTheme defaults = theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
late final AppBarTheme defaults = Theme.of(context).useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), _kMaxTitleTextScaleFactor); // TODO(tahatesser): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769.
final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
final double topPadding = primary ? MediaQuery.viewPaddingOf(context).top : 0;
final double collapsedHeight = settings.minExtent - topPadding - bottomHeight;
final double scrollUnderHeight = settings.maxExtent - (settings.minExtent / textScaleFactor) + bottomHeight;
final _ScrollUnderFlexibleConfig config;
switch (variant) {
case _ScrollUnderFlexibleVariant.medium:
config = _MediumScrollUnderFlexibleConfig(context);
case _ScrollUnderFlexibleVariant.large:
config = _LargeScrollUnderFlexibleConfig(context);
}
final _ScrollUnderFlexibleConfig config = configBuilder(context);
assert(
config.expandedTitlePadding.isNonNegative,
'The _ExpandedTitleWithPadding widget assumes that the expanded title padding is non-negative. '
'Update its implementation to handle negative padding.',
);

late final Widget? expandedTitle;
if (title != null) {
final TextStyle? expandedTextStyle = titleTextStyle
?? appBarTheme.titleTextStyle
?? config.expandedTextStyle?.copyWith(
color: foregroundColor ?? appBarTheme.foregroundColor ?? defaults.foregroundColor,
);
expandedTitle = config.expandedTextStyle != null
? DefaultTextStyle(
style: expandedTextStyle!,
child: title!,
)
: title;
}
final TextStyle? expandedTextStyle = titleTextStyle
?? appBarTheme.titleTextStyle
?? config.expandedTextStyle?.copyWith(color: foregroundColor ?? appBarTheme.foregroundColor ?? defaults.foregroundColor);

EdgeInsetsGeometry expandedTitlePadding() {
final EdgeInsets padding = config.expandedTitlePadding!.resolve(Directionality.of(context));
if (bottomHeight > 0) {
return EdgeInsets.fromLTRB(padding.left, 0, padding.right, bottomHeight);
}
if (theme.useMaterial3) {
final TextStyle textStyle = config.expandedTextStyle!;
// Substract the bottom line height from the bottom padding.
// TODO(tahatesser): Figure out why this is done.
// https://github.com/flutter/flutter/issues/120672
final double adjustBottomPadding = padding.bottom
- (textStyle.fontSize! * textStyle.height! - textStyle.fontSize!) / 2;
return EdgeInsets.fromLTRB(
padding.left,
0,
padding.right,
adjustBottomPadding / textScaleFactor,
);
}
return padding;
}
final Widget? expandedTitle = switch ((title, expandedTextStyle)) {
(null, _) => null,
(final Widget title, null) => title,
(final Widget title, final TextStyle textStyle) => DefaultTextStyle(style: textStyle, child: title),
};

final EdgeInsets resolvedTitlePadding = config.expandedTitlePadding.resolve(Directionality.of(context));
final EdgeInsetsGeometry expandedTitlePadding = bottomHeight > 0
? resolvedTitlePadding.copyWith(bottom: 0)
: resolvedTitlePadding;

// Set maximum text scale factor to [_kMaxTitleTextScaleFactor] for the
// title to keep the visual hierarchy the same even with larger font
Expand All @@ -2162,35 +2129,167 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget {
// `MediaQuery.textScaleFactorOf(context)`.
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor),
// This column will assume the full height of the parent Stack.
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: collapsedHeight + topPadding),
),
Padding(padding: EdgeInsets.only(top: settings.minExtent - bottomHeight)),
Flexible(
child: ClipRect(
child: OverflowBox(
minHeight: scrollUnderHeight,
maxHeight: scrollUnderHeight,
alignment: Alignment.bottomLeft,
child: Container(
alignment: AlignmentDirectional.bottomStart,
padding: expandedTitlePadding(),
child: expandedTitle,
),
child: _ExpandedTitleWithPadding(
padding: expandedTitlePadding,
maxExtent: settings.maxExtent - settings.minExtent,
child: expandedTitle,
),
),
),
// Reserve space for AppBar.bottom, which is a sibling of this widget,
// on the parent Stack.
if (bottomHeight > 0) Padding(padding: EdgeInsets.only(bottom: bottomHeight)),
],
),
);
}
}

// A widget that bottom-start aligns its child (the expanded title widget), and
// insets the child according to the specified padding.
//
// This widget gives the child an infinite max height constraint, and will also
// attempt to vertically limit the child's bounding box (not including the
// padding) to within the y range [0, maxExtent], to make sure the child is
// visible when the AppBar is fully expanded.
class _ExpandedTitleWithPadding extends SingleChildRenderObjectWidget {
const _ExpandedTitleWithPadding({
required this.padding,
required this.maxExtent,
super.child,
});

final EdgeInsetsGeometry padding;
final double maxExtent;

@override
_RenderExpandedTitleBox createRenderObject(BuildContext context) {
final TextDirection textDirection = Directionality.of(context);
return _RenderExpandedTitleBox(
padding.resolve(textDirection),
AlignmentDirectional.bottomStart.resolve(textDirection),
maxExtent,
null,
);
}

@override
void updateRenderObject(BuildContext context, _RenderExpandedTitleBox renderObject) {
final TextDirection textDirection = Directionality.of(context);
renderObject
..padding = padding.resolve(textDirection)
..titleAlignment = AlignmentDirectional.bottomStart.resolve(textDirection)
..maxExtent = maxExtent;
}
}

class _RenderExpandedTitleBox extends RenderShiftedBox {
_RenderExpandedTitleBox(this._padding, this._titleAlignment, this._maxExtent, super.child);

EdgeInsets get padding => _padding;
EdgeInsets _padding;
set padding(EdgeInsets value) {
if (_padding == value) {
return;
}
assert(value.isNonNegative);
_padding = value;
markNeedsLayout();
}

Alignment get titleAlignment => _titleAlignment;
Alignment _titleAlignment;
set titleAlignment(Alignment value) {
if (_titleAlignment == value) {
return;
}
_titleAlignment = value;
markNeedsLayout();
}

double get maxExtent => _maxExtent;
double _maxExtent;
set maxExtent(double value) {
if (_maxExtent == value) {
return;
}
_maxExtent = value;
markNeedsLayout();
}

@override
double computeMaxIntrinsicHeight(double width) {
final RenderBox? child = this.child;
return child == null ? 0.0 : child.getMaxIntrinsicHeight(math.max(0, width - padding.horizontal)) + padding.vertical;
}

@override
double computeMaxIntrinsicWidth(double height) {
final RenderBox? child = this.child;
return child == null ? 0.0 : child.getMaxIntrinsicWidth(double.infinity) + padding.horizontal;
}

@override
double computeMinIntrinsicHeight(double width) {
final RenderBox? child = this.child;
return child == null ? 0.0 : child.getMinIntrinsicHeight(math.max(0, width - padding.horizontal)) + padding.vertical;
}

@override
double computeMinIntrinsicWidth(double height) {
final RenderBox? child = this.child;
return child == null ? 0.0 : child.getMinIntrinsicWidth(double.infinity) + padding.horizontal;
}

Size _computeSize(BoxConstraints constraints, ChildLayouter layoutChild) {
final RenderBox? child = this.child;
if (child == null) {
return Size.zero;
}
layoutChild(child, constraints.widthConstraints().deflate(padding));
return constraints.biggest;
}

@override
Size computeDryLayout(BoxConstraints constraints) => _computeSize(constraints, ChildLayoutHelper.dryLayoutChild);

@override
void performLayout() {
final RenderBox? child = this.child;
if (child == null) {
this.size = constraints.smallest;
return;
}
final Size size = this.size = _computeSize(constraints, ChildLayoutHelper.layoutChild);
final Size childSize = child.size;

assert(padding.isNonNegative);
assert(titleAlignment.y == 1.0);
// yAdjustement is the minimum additional y offset to shift the child in
// the visible vertical space when AppBar is fully expanded. The goal is to
// prevent the expanded title from being clipped when the expanded title
// widget + the bottom padding is too tall to fit in the flexible space (the
// top padding is basically ignored since the expanded title is
// bottom-aligned).
final double yAdjustement = clampDouble(childSize.height + padding.bottom - maxExtent, 0, padding.bottom);
final double offsetY = size.height - childSize.height - padding.bottom + yAdjustement;
final double offsetX = (titleAlignment.x + 1) / 2 * (size.width - padding.horizontal - childSize.width) + padding.left;

final BoxParentData childParentData = child.parentData! as BoxParentData;
childParentData.offset = Offset(offsetX, offsetY);
}
}

mixin _ScrollUnderFlexibleConfig {
TextStyle? get collapsedTextStyle;
TextStyle? get expandedTextStyle;
EdgeInsetsGeometry? get expandedTitlePadding;
EdgeInsetsGeometry get expandedTitlePadding;
}

// Hand coded defaults based on Material Design 2.
Expand Down Expand Up @@ -2298,7 +2397,7 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
_textTheme.headlineSmall?.apply(color: _colors.onSurface);

@override
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20);
}

class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
Expand All @@ -2321,7 +2420,7 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig {
_textTheme.headlineMedium?.apply(color: _colors.onSurface);

@override
EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28);
}

// END GENERATED TOKEN PROPERTIES - AppBar
2 changes: 1 addition & 1 deletion packages/flutter/lib/src/material/chip.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2215,7 +2215,7 @@ class _ChipDefaultsM3 extends ChipThemeData {
EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0);

/// The chip at text scale 1 starts with 8px on each side and as text scaling
/// gets closer to 2 the label padding is linearly interpolated from 8px to 4px.
/// gets closer to 2, the label padding is linearly interpolated from 8px to 4px.
/// Once the widget has a text scaling of 2 or higher than the label padding
/// remains 4px.
@override
Expand Down
Loading

0 comments on commit 73e1f23

Please sign in to comment.