Flutter ne peut pas conserver l'état des onglets à l'aide de PageTransitionSwitcher

Nov 28 2020

J'ai du mal avec le package d' animations et je souhaite utiliser l'animation avec BottomNavigationBar. Sans animation, je peux enregistrer mon état en utilisant IndexedStack. Si j'emballe IndexedStack dans PageTransitionSwitcher, cela ne fonctionne pas. En particulier:

  • les animations ne s'affichent pas mais l'état est conservé
  • si j'utilise la propriété clé de mon IndexedStack, les animations s'affichent mais l'état ne fonctionne pas.

Comment puis-je y remédier? Je ne sais pas comment configurer les clés. Merci beaucoup!!

class MainScreen extends StatefulWidget {
  static String id = 'loading_screen';

  @override
  _MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int _selectedPage = 0;
  List<Widget> pageList = List<Widget>();

  @override
  void initState() {
    pageList.add(PrimoTab());
    pageList.add(SecondoTab());
    pageList.add(TerzoTab());
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom tab'),
      ),
      body: PageTransitionSwitcher(
        transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
          return SharedAxisTransition(
            animation: primaryAnimation,
            secondaryAnimation: secondaryAnimation,
            child: child,
            transitionType: SharedAxisTransitionType.horizontal,
          );
        },
        child: IndexedStack(
          index: _selectedPage,
          children: pageList,
          //key: ValueKey<int>(_selectedPage), NOT WORKING
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.directions_car),
            label: 'First Page',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.airplanemode_active),
            label: 'Second Page',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.directions_bike),
            label: 'Third Page',
          ),
        ],
        currentIndex: _selectedPage,
        selectedItemColor: Colors.lightGreen,
        unselectedItemColor: Colors.lightBlueAccent,
        onTap: _onItemTapped,
      ),
    );
  }

  void _onItemTapped(int index) {
    setState(() {
      _selectedPage = index;
    });
  }
}

Chaque onglet est juste une colonne avec deux widgets de texte, un bouton et un compteur de texte (pour tester l'état de chaque onglet):

class PrimoTab extends StatefulWidget {

  @override
  _PrimoTabState createState() => _PrimoTabState();
}

class _PrimoTabState extends State<PrimoTab> {
  int cont = -1;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(
              'TAB 1 - TEXT 1',
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(
              'TAB 1 - TEXT 2',
            ),
          ),
          FlatButton(
            onPressed: () {
              setState(() {
                cont++;
              });
            },
            child: Text("CLICK"),
          ),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Text(
              'Valore contatore $cont',
            ),
          ),
        ],
      ),
    );
  }
}

MISE À JOUR 1: en utilisant uniquement

pageList[_selectedPage],

au lieu de

IndexedStack(
...
)

mais ne fonctionne pas (animations ok mais l'état n'est pas conservé)

MISE À JOUR 2 AVEC SOLUTION (main.dart):

void main() {
  runApp(
    MaterialApp(
      home: MainScreen(),
    ),
  );
}

class MainScreen extends StatefulWidget {
  static String id = 'loading_screen';

  @override
  _MainScreenState createState() => _MainScreenState();
}

class _MainScreenState extends State<MainScreen> {
  int _selectedPage = 0;
  List<Widget> pageList = List<Widget>();

  @override
  void initState() {
    pageList.add(PrimoTab());
    pageList.add(SecondoTab());
    pageList.add(TerzoTab());
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bottom tab'),
      ),
      body: AnimatedIndexedStack(
        index: _selectedPage,
        children: pageList,
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.directions_car),
            label: 'First Page',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.airplanemode_active),
            label: 'Second Page',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.directions_bike),
            label: 'Third Page',
          ),
        ],
        currentIndex: _selectedPage,
        selectedItemColor: Colors.lightGreen,
        unselectedItemColor: Colors.lightBlueAccent,
        onTap: _onItemTapped,
      ),
    );
  }

  void _onItemTapped(int index) {
    setState(() {
      _selectedPage = index;
    });
  }
}

class AnimatedIndexedStack extends StatefulWidget {
  final int index;
  final List<Widget> children;

  const AnimatedIndexedStack({
    Key key,
    this.index,
    this.children,
  }) : super(key: key);

  @override
  _AnimatedIndexedStackState createState() => _AnimatedIndexedStackState();
}

class _AnimatedIndexedStackState extends State<AnimatedIndexedStack>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;
  int _index;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 150),
    );
    _animation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(
        parent: _controller,
        curve: Curves.ease,
      ),
    );

    _index = widget.index;
    _controller.forward();
    super.initState();
  }

  @override
  void didUpdateWidget(AnimatedIndexedStack oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.index != _index) {
      _controller.reverse().then((_) {
        setState(() => _index = widget.index);
        _controller.forward();
      });
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animation,
      builder: (context, child) {
        return Opacity(
          opacity: _controller.value,
          child: Transform.scale(
            scale: 1.015 - (_controller.value * 0.015),
            child: child,
          ),
        );
      },
      child: IndexedStack(
        index: _index,
        children: widget.children,
      ),
    );
  }
}

Réponses

1 yellowgray Nov 29 2020 at 06:27

Il existe plusieurs façons de régler votre situation. Aucun d'entre eux n'est parfaitement simple et il y a déjà beaucoup de discussions ici et ici , essayant de fusionner IndexedStackavec PageTransitionSwitcher. Aucune solution jusqu'à présent, j'ai vu.

Je collecte les moyens suivants pour y parvenir:

  1. Stockez l'état ailleurs et passez à l'enfant. Je n'ai vu aucune méthode pouvant empêcher la PageTransitionSwitcherreconstruction du widget enfant. Si vous n'extrayez pas la reconstruction du widget enfant , c'est peut-être la méthode la plus simple à utiliser.

  2. Utilisez Custom IndexedStackavec animation. comme ceci et ceci . Cela fonctionne bien avec la fonctionnalité d'IndexStack que les enfants ne reconstruiront pas, mais l'animation n'est pas aussi bonne PageTransitionSwitcheret elle ne peut afficher qu'un seul widget à la fois.