[go: nahoru, domu]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove textScaleFactor dependent logic from AppBar #128112

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NICE

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

}

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);
TahaTesser marked this conversation as resolved.
Show resolved Hide resolved
final AppBarTheme defaults = theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
late final AppBarTheme defaults = Theme.of(context).useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
TahaTesser marked this conversation as resolved.
Show resolved Hide resolved
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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slick but I'm not sure the logic is the same or even simpler than before.

    if (title != null) {
      ...
      expandedTitle = config.expandedTextStyle != null
        ? DefaultTextStyle(
            style: expandedTextStyle!,
            child: title!,
          )
        : title;
    }

Would only wrap the title with the DefaultTextStyle if config.expandedTextStyle was non-null. The new code checks expandedTextStyle which incorporates the widget/theme defaults. I don't know why the code was the way it was, so I suppose if the change doesn't break anything it's OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me!

config.expandedTextStyle != null existed before I added title ?? appBarTheme.titleTextStyle. I should have updated it to just expandedTextStyle != null. Yours refactor makes it much cleaner and hard to miss.

(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