Skip to content

Animations in NavigationDestination icons don't work well #122811

@math1man

Description

@math1man

Steps to Reproduce

  1. Run the code sample at https://dartpad.dev/?id=70f5ce75c3fdb7b614e9253ebd8cc2c9
  2. Switch between destinations in the NavigationBar

Expected results:

When switching destinations, the destination icon should animate. The expected result can be produced by uncommenting line 49 in the dartpad example that adds a GlobaKey to the AnimatedIcon.

Actual results:

The icon does not animate and jumps from start to end state.

This issue was introduced with #116888. That change adds a UniqueKey to a widget within NavigationDestination's construction of its icon, which breaks the animated icon's ability to retain state without using a GlobalKey of some sort. Though adding the GlobalKey is a reasonably easy fix to the issue, it's very unintuitive and in my opinion shouldn't be required.

Code sample

(copied from https://dartpad.dev/?id=70f5ce75c3fdb7b614e9253ebd8cc2c9)

import 'package:flutter/material.dart';

const Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark().copyWith(
        scaffoldBackgroundColor: darkBlue,
      ),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Text(
            'Hello, World!',
            style: Theme.of(context).textTheme.headlineMedium,
          ),
        ),
        bottomNavigationBar: AnimatedNavigationBar(),
      ),
    );
  }
}

class AnimatedNavigationBar extends StatefulWidget {
  @override
  State<AnimatedNavigationBar> createState() => _AnimatedNavigationBarState();
}

class _AnimatedNavigationBarState extends State<AnimatedNavigationBar> {
  int selectedIndex = 0;

  @override
  Widget build(BuildContext context) => NavigationBar(
        selectedIndex: selectedIndex,
        onDestinationSelected: (index) => setState(() {
          selectedIndex = index;
        }),
        destinations: [
          for (var i = 0; i < 3; i++)
            NavigationDestination(
              icon: AnimatedIcon(
                // Uncomment the following line to fix the bug.
                // key: GlobalObjectKey(i),
                selected: selectedIndex == i,
              ),
              label: 'Destination$i',
            ),
        ],
      );
}

class AnimatedIcon extends StatefulWidget {
  const AnimatedIcon({super.key, required this.selected});

  final bool selected;

  @override
  State<AnimatedIcon> createState() => _AnimatedIconState();
}

class _AnimatedIconState extends State<AnimatedIcon>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      lowerBound: 0.5,
      upperBound: 1,
      duration: const Duration(seconds: 1),
      vsync: this,
    );
    if (widget.selected) controller.value = 1;
  }

  @override
  void didUpdateWidget(AnimatedIcon oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.selected != oldWidget.selected) {
      if (widget.selected) {
        controller.forward();
      } else {
        controller.reset();
      }
    }
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => ScaleTransition(
        scale: controller,
        child: const Icon(Icons.favorite),
      );
}

Internal bug b/273309891

Metadata

Metadata

Assignees

Labels

P2Important issues not at the top of the work listf: material designflutter/packages/flutter/material repository.frameworkflutter/packages/flutter repository. See also f: labels.r: fixedIssue is closed as already fixed in a newer version

Type

No type

Projects

Status

Done (PR merged)

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions