Feat: Animate fill for material app bar (#163913)

Feat: Animate fill for material app bar
fixes: #162988 

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.
This commit is contained in:
Kishan Rathore 2025-05-07 22:22:38 +05:30 committed by GitHub
parent 708c0eb185
commit b0f5c8ce03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 55 additions and 1 deletions

View File

@ -221,6 +221,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
this.useDefaultSemanticsOrder = true,
this.clipBehavior,
this.actionsPadding,
this.animateColor = false,
}) : assert(elevation == null || elevation >= 0.0),
preferredSize = _PreferredAppBarSize(toolbarHeight, bottom?.preferredSize.height);
@ -773,6 +774,9 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget {
/// {@endtemplate}
final EdgeInsetsGeometry? actionsPadding;
/// Whether the color should be animated.
final bool animateColor;
bool _getEffectiveCenterTitle(ThemeData theme) {
bool platformCenter() {
switch (theme.platform) {
@ -1213,6 +1217,7 @@ class _AppBarState extends State<AppBar> {
??
(theme.useMaterial3 ? theme.colorScheme.surfaceTint : null),
shape: widget.shape ?? appBarTheme.shape ?? defaults.shape,
animateColor: widget.animateColor,
child: Semantics(explicitChildNodes: true, child: appBar),
),
),

View File

@ -204,6 +204,7 @@ class Material extends StatefulWidget {
this.clipBehavior = Clip.none,
this.animationDuration = kThemeChangeDuration,
this.child,
this.animateColor = false,
}) : assert(elevation >= 0.0),
assert(!(shape != null && borderRadius != null)),
assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null)));
@ -218,6 +219,9 @@ class Material extends StatefulWidget {
/// the shape is rectangular, and the default color.
final MaterialType type;
/// Whether the color should be animated.
final bool animateColor;
/// {@template flutter.material.material.elevation}
/// The z-coordinate at which to place this material relative to its parent.
///
@ -522,7 +526,7 @@ class _MaterialState extends State<Material> with TickerProviderStateMixin {
elevation: widget.elevation,
color: color,
shadowColor: modelShadowColor,
animateColor: false,
animateColor: widget.animateColor,
child: contents,
);
}

View File

@ -2244,17 +2244,21 @@ void main() {
required double contentHeight,
bool reverse = false,
bool includeFlexibleSpace = false,
bool animateColor = false,
double? scrolledUnderElevation,
}) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
elevation: 0,
scrolledUnderElevation: scrolledUnderElevation,
backgroundColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor;
}),
title: const Text('AppBar'),
flexibleSpace:
includeFlexibleSpace ? const FlexibleSpaceBar(title: Text('FlexibleSpace')) : null,
animateColor: animateColor,
),
body: ListView(
reverse: reverse,
@ -2339,6 +2343,39 @@ void main() {
expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight);
});
testWidgets('backgroundColor animation', (WidgetTester tester) async {
await tester.pumpWidget(
buildAppBar(contentHeight: 1200.0, scrolledUnderElevation: 0, animateColor: true),
);
expect(getAppBarAnimatedBackgroundColor(tester), defaultColor);
TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0));
await gesture.moveBy(const Offset(0.0, -kToolbarHeight));
await gesture.up();
await tester.pump();
expect(getAppBarAnimatedBackgroundColor(tester), defaultColor);
await tester.pumpAndSettle();
expect(getAppBarAnimatedBackgroundColor(tester), scrolledColor);
gesture = await tester.startGesture(const Offset(50.0, 300.0));
await gesture.moveBy(const Offset(0.0, kToolbarHeight));
await gesture.up();
await tester.pump();
expect(getAppBarAnimatedBackgroundColor(tester), scrolledColor);
// Check intermediate color values.
await tester.pump(const Duration(milliseconds: 50));
expect(getAppBarAnimatedBackgroundColor(tester), isSameColorAs(const Color(0xFF00C33C)));
await tester.pump(const Duration(milliseconds: 50));
expect(getAppBarAnimatedBackgroundColor(tester), isSameColorAs(const Color(0xFF0039C6)));
await tester.pumpAndSettle();
expect(getAppBarAnimatedBackgroundColor(tester), defaultColor);
});
testWidgets('backgroundColor with FlexibleSpace', (WidgetTester tester) async {
await tester.pumpWidget(buildAppBar(contentHeight: 1200.0, includeFlexibleSpace: true));

View File

@ -5,6 +5,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
Finder findAppBarPhysicalModel() {
return find.descendant(of: find.byType(AppBar), matching: find.byType(PhysicalModel)).first;
}
Color? getAppBarAnimatedBackgroundColor(WidgetTester tester) {
return tester.widget<PhysicalModel>(findAppBarPhysicalModel()).color;
}
Finder findAppBarMaterial() {
return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first;
}