Unverified Commit 1f5fcb74 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Speed up AnimatedSwitcher. (#17265)

This optimizes the AnimatedSwitcher so that it tags the right widget with its keyed subtree, and avoids rebuilding the transition unnecessarily.

This significantly improves the performance of Chips (which uses AnimatedSwitcher to swap out it's avatar and delete icon children).
parent 8a4db32b
...@@ -302,13 +302,6 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat ...@@ -302,13 +302,6 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat
super.dispose(); super.dispose();
} }
static Widget _animatedSwitcherLayoutBuilder(List<Widget> children) {
return new Stack(
children: children,
alignment: Alignment.center,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
...@@ -338,7 +331,6 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat ...@@ -338,7 +331,6 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat
duration: _kFrontLayerSwitchDuration, duration: _kFrontLayerSwitchDuration,
switchOutCurve: switchOutCurve, switchOutCurve: switchOutCurve,
switchInCurve: switchInCurve, switchInCurve: switchInCurve,
layoutBuilder: _animatedSwitcherLayoutBuilder,
child: _category == null child: _category == null
? const _FlutterLogo() ? const _FlutterLogo()
: new IconButton( : new IconButton(
...@@ -358,7 +350,6 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat ...@@ -358,7 +350,6 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat
duration: _kFrontLayerSwitchDuration, duration: _kFrontLayerSwitchDuration,
switchOutCurve: switchOutCurve, switchOutCurve: switchOutCurve,
switchInCurve: switchInCurve, switchInCurve: switchInCurve,
layoutBuilder: _animatedSwitcherLayoutBuilder,
child: _category != null child: _category != null
? new _DemosPage(_category) ? new _DemosPage(_category)
: new _CategoriesPage( : new _CategoriesPage(
......
...@@ -1328,7 +1328,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip ...@@ -1328,7 +1328,7 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
} }
} }
Widget _wrapWithTooltip(Widget child, String tooltip, VoidCallback callback) { Widget _wrapWithTooltip(String tooltip, VoidCallback callback, Widget child) {
if (child == null || callback == null || tooltip == null) { if (child == null || callback == null || tooltip == null) {
return child; return child;
} }
...@@ -1343,17 +1343,17 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip ...@@ -1343,17 +1343,17 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
return null; return null;
} }
return _wrapWithTooltip( return _wrapWithTooltip(
widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context)?.deleteButtonTooltip,
widget.onDeleted,
new InkResponse( new InkResponse(
onTap: widget.isEnabled ? widget.onDeleted : null, onTap: widget.isEnabled ? widget.onDeleted : null,
child: new IconTheme( child: new IconTheme(
data: theme.iconTheme.copyWith( data: theme.iconTheme.copyWith(
color: (widget.deleteIconColor ?? chipTheme.deleteIconColor) ?? theme.iconTheme.color, color: widget.deleteIconColor ?? chipTheme.deleteIconColor,
), ),
child: widget.deleteIcon, child: widget.deleteIcon,
), ),
), ),
widget.deleteButtonTooltipMessage ?? MaterialLocalizations.of(context)?.deleteButtonTooltip,
widget.onDeleted,
); );
} }
...@@ -1388,42 +1388,43 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip ...@@ -1388,42 +1388,43 @@ class _RawChipState extends State<RawChip> with TickerProviderStateMixin<RawChip
); );
}, },
child: _wrapWithTooltip( child: _wrapWithTooltip(
new _ChipRenderWidget( widget.tooltip,
theme: new _ChipRenderTheme( widget.onPressed,
label: new DefaultTextStyle( new _ChipRenderWidget(
overflow: TextOverflow.fade, theme: new _ChipRenderTheme(
textAlign: TextAlign.start, label: new DefaultTextStyle(
maxLines: 1, overflow: TextOverflow.fade,
softWrap: false, textAlign: TextAlign.start,
style: widget.labelStyle ?? chipTheme.labelStyle, maxLines: 1,
child: widget.label, softWrap: false,
), style: widget.labelStyle ?? chipTheme.labelStyle,
avatar: new AnimatedSwitcher( child: widget.label,
child: widget.avatar,
duration: _kDrawerDuration,
switchInCurve: Curves.fastOutSlowIn,
),
deleteIcon: new AnimatedSwitcher(
child: _buildDeleteIcon(context, theme, chipTheme),
duration: _kDrawerDuration,
switchInCurve: Curves.fastOutSlowIn,
),
brightness: chipTheme.brightness,
padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
labelPadding: (widget.labelPadding ?? chipTheme.labelPadding).resolve(textDirection),
showAvatar: hasAvatar,
showCheckmark: widget.showCheckmark,
canTapBody: canTap,
), ),
value: widget.selected, avatar: new AnimatedSwitcher(
checkmarkAnimation: checkmarkAnimation, child: widget.avatar,
enableAnimation: enableAnimation, duration: _kDrawerDuration,
avatarDrawerAnimation: avatarDrawerAnimation, switchInCurve: Curves.fastOutSlowIn,
deleteDrawerAnimation: deleteDrawerAnimation, ),
isEnabled: widget.isEnabled, deleteIcon: new AnimatedSwitcher(
child: _buildDeleteIcon(context, theme, chipTheme),
duration: _kDrawerDuration,
switchInCurve: Curves.fastOutSlowIn,
),
brightness: chipTheme.brightness,
padding: (widget.padding ?? chipTheme.padding).resolve(textDirection),
labelPadding: (widget.labelPadding ?? chipTheme.labelPadding).resolve(textDirection),
showAvatar: hasAvatar,
showCheckmark: widget.showCheckmark,
canTapBody: canTap,
), ),
widget.tooltip, value: widget.selected,
widget.onPressed), checkmarkAnimation: checkmarkAnimation,
enableAnimation: enableAnimation,
avatarDrawerAnimation: avatarDrawerAnimation,
deleteDrawerAnimation: deleteDrawerAnimation,
isEnabled: widget.isEnabled,
),
),
), ),
), ),
); );
......
...@@ -1029,10 +1029,10 @@ void main() { ...@@ -1029,10 +1029,10 @@ void main() {
platform: TargetPlatform.android, platform: TargetPlatform.android,
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
); );
final ChipThemeData chipTheme = themeData.chipTheme; final ChipThemeData defaultChipTheme = themeData.chipTheme;
bool value = false; bool value = false;
Widget buildApp({ Widget buildApp({
ChipThemeData theme, ChipThemeData chipTheme,
Widget avatar, Widget avatar,
Widget deleteIcon, Widget deleteIcon,
bool isSelectable: true, bool isSelectable: true,
...@@ -1040,12 +1040,12 @@ void main() { ...@@ -1040,12 +1040,12 @@ void main() {
bool isDeletable: true, bool isDeletable: true,
bool showCheckmark: true, bool showCheckmark: true,
}) { }) {
theme ??= chipTheme; chipTheme ??= defaultChipTheme;
return _wrapForChip( return _wrapForChip(
child: new Theme( child: new Theme(
data: themeData, data: themeData,
child: new ChipTheme( child: new ChipTheme(
data: theme, data: chipTheme,
child: new StatefulBuilder(builder: (BuildContext context, StateSetter setState) { child: new StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return new RawChip( return new RawChip(
showCheckmark: showCheckmark, showCheckmark: showCheckmark,
...@@ -1054,7 +1054,7 @@ void main() { ...@@ -1054,7 +1054,7 @@ void main() {
avatar: avatar, avatar: avatar,
deleteIcon: deleteIcon, deleteIcon: deleteIcon,
isEnabled: isSelectable || isPressable, isEnabled: isSelectable || isPressable,
shape: theme.shape, shape: chipTheme.shape,
selected: isSelectable ? value : null, selected: isSelectable ? value : null,
label: new Text('$value'), label: new Text('$value'),
onSelected: isSelectable onSelected: isSelectable
...@@ -1085,13 +1085,13 @@ void main() { ...@@ -1085,13 +1085,13 @@ void main() {
DefaultTextStyle labelStyle = getLabelStyle(tester); DefaultTextStyle labelStyle = getLabelStyle(tester);
// Check default theme for enabled widget. // Check default theme for enabled widget.
expect(materialBox, paints..path(color: chipTheme.backgroundColor)); expect(materialBox, paints..path(color: defaultChipTheme.backgroundColor));
expect(iconData.color, equals(const Color(0xde000000))); expect(iconData.color, equals(const Color(0xde000000)));
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
await tester.tap(find.byType(RawChip)); await tester.tap(find.byType(RawChip));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
materialBox = getMaterialBox(tester); materialBox = getMaterialBox(tester);
expect(materialBox, paints..path(color: chipTheme.selectedColor)); expect(materialBox, paints..path(color: defaultChipTheme.selectedColor));
await tester.tap(find.byType(RawChip)); await tester.tap(find.byType(RawChip));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
...@@ -1100,7 +1100,7 @@ void main() { ...@@ -1100,7 +1100,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
materialBox = getMaterialBox(tester); materialBox = getMaterialBox(tester);
labelStyle = getLabelStyle(tester); labelStyle = getLabelStyle(tester);
expect(materialBox, paints..path(color: chipTheme.disabledColor)); expect(materialBox, paints..path(color: defaultChipTheme.disabledColor));
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
// Apply a custom theme. // Apply a custom theme.
...@@ -1108,14 +1108,14 @@ void main() { ...@@ -1108,14 +1108,14 @@ void main() {
const Color customColor2 = const Color(0xdeadbeef); const Color customColor2 = const Color(0xdeadbeef);
const Color customColor3 = const Color(0xbeefcafe); const Color customColor3 = const Color(0xbeefcafe);
const Color customColor4 = const Color(0xaddedabe); const Color customColor4 = const Color(0xaddedabe);
final ChipThemeData customTheme = chipTheme.copyWith( final ChipThemeData customTheme = defaultChipTheme.copyWith(
brightness: Brightness.dark, brightness: Brightness.dark,
backgroundColor: customColor1, backgroundColor: customColor1,
disabledColor: customColor2, disabledColor: customColor2,
selectedColor: customColor3, selectedColor: customColor3,
deleteIconColor: customColor4, deleteIconColor: customColor4,
); );
await tester.pumpWidget(buildApp(theme: customTheme)); await tester.pumpWidget(buildApp(chipTheme: customTheme));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
materialBox = getMaterialBox(tester); materialBox = getMaterialBox(tester);
iconData = getIconData(tester); iconData = getIconData(tester);
...@@ -1134,7 +1134,7 @@ void main() { ...@@ -1134,7 +1134,7 @@ void main() {
// Check custom theme with disabled widget. // Check custom theme with disabled widget.
await tester.pumpWidget(buildApp( await tester.pumpWidget(buildApp(
theme: customTheme, chipTheme: customTheme,
isSelectable: false, isSelectable: false,
isPressable: false, isPressable: false,
isDeletable: true, isDeletable: true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment