带有 IndexedStack 的动画切换器

问题描述 投票:0回答:6

我必须使用 IndexedStack 来维护 BottomNavigationBar 的小部件的状态。 现在我想使用 AnimatedSwitcher (或替代方案)在切换选项卡时创建动画。 我在让 AnimatedSwitcher 在 IndexedStack 更改时触发时遇到问题。我将 IndexedStack 作为 AnimatedSwitcher 的子级,这显然会导致 AnimatedSwitcher 不会触发,因为 IndexedStack 小部件不会更改,只是它是子级。

body: AnimatedSwitcher(  
  duration: Duration(milliseconds: 200),  
  child: IndexedStack(  
    children: _tabs.map((t) => t.widget).toList(),  
    index: _currentIndex,  
  ),  
)

有什么办法可以解决这个问题吗?通过手动触发 AnimatedSwitcher,还是通过使用不同的方法来创建动画? 我也尝试更改密钥,但这显然会导致每次创建新状态时都会创建一个新的 IndexedStack,因此选项卡的状态也会丢失。

flutter flutter-layout flutter-animation
6个回答
38
投票

这是将

IndexedStack
与动画一起使用的更简洁的方式,我创建了一个
FadeIndexedStack
小部件。

https://gist.github.com/diegoveloper/1cd23e79a31d0c18a67424f0cbdfd7ad

用法

body: FadeIndexedStack(  
    //this is optional
    //duration: Duration(seconds: 1),
    children: _tabs.map((t) => t.widget).toList(),  
    index: _currentIndex,  
  ),  


8
投票

如果需要使用IndexedStack。

您可以添加自定义动画并在更改选项卡时触发它,如下所示:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
  final List<Widget> myTabs = [
    Tab(text: 'one'),
    Tab(text: 'two'),
    Tab(text: 'three'),
  ];

  AnimationController _animationController;
  TabController _tabController;
  int _tabIndex = 0;
  Animation animation;

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

  @override
  void initState() {
    _tabController = TabController(length: 3, vsync: this);
    _animationController = AnimationController(
      vsync: this,
      value: 1.0,
      duration: Duration(milliseconds: 500),
    );
    _tabController.addListener(_handleTabSelection);
    animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
    super.initState();
  }

  _handleTabSelection() {
    if (!_tabController.indexIsChanging) {
      setState(() {
        _tabIndex = _tabController.index;
      });
      _animationController.reset();
      _animationController.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    List<Widget> _tabs = [
      MyAnimation(
        animation: animation,
        child: Text('first tab'),
      ),
      MyAnimation(
        animation: animation,
        child: Column(
          children: List.generate(20, (index) => Text('line: $index')).toList(),
        ),
      ),
      MyAnimation(
        animation: animation,
        child: Text('third tab'),
      ),
    ];

    return Scaffold(
      appBar: AppBar(),
      bottomNavigationBar: TabBar(
        controller: _tabController,
        labelColor: Colors.redAccent,
        isScrollable: true,
        tabs: myTabs,
      ),
      body: IndexedStack(
        children: _tabs,
        index: _tabIndex,
      ),
    );
  }
}

class MyAnimation extends AnimatedWidget {
  MyAnimation({key, animation, this.child})
      : super(
          key: key,
          listenable: animation,
        );

  final Widget child;

  @override
  Widget build(BuildContext context) {
    Animation<double> animation = listenable;
    return Opacity(
      opacity: animation.value,
      child: child,
    );
  }
}

1
投票

尝试将 key 添加到您的 IndexedStack 中,并向您的 AnimatedSwitcher 小部件添加一个 transitionBuilder 方法,就像这样......

AnimatedSwitcher(
            duration: Duration(milliseconds: 1200),
            transitionBuilder: (child, animation) => SizeTransition(
              sizeFactor: animation,
              child: IndexedStack(
                key: ValueKey<int>(navigationIndex.state),
                index: navigationIndex.state,
                children: _tabs.map((t) => t.widget).toList(),  
              ),
            ),
            child: IndexedStack(
              key: ValueKey<int>(navigationIndex.state), //using StateProvider<int>
              index: navigationIndex.state,
              children: _tabs.map((t) => t.widget).toList(),  
            ),
          ),

还有其他很酷的过渡,如 ScaleTransition、SizeTransition、FadeTransition


1
投票

以下是如何操作的示例:

PageTransitionSwitcher(
   duration: Duration(milliseconds: 250),
      transitionBuilder: (widget, anim1, anim2) {
    return FadeScaleTransition(
      animation: anim1,
      child: widget,
    );
  },
  child: IndexedStack(
    index: _currentIndex,
    key: ValueKey<int>(_currentIndex),
    children: [
      Page1(),
      Page2(),
      Page3()
    ],
  ),
);

0
投票

解决此问题的方法如下:

  1. 首先使用下面的代码创建一个新的小部件。

  2. 这个小部件使用

    PageView
    ,默认情况下会给你一个漂亮的滑动动画,但如果你愿意,你可以自定义动画。

  3. 要保持子级的状态,只需在每个子级的小部件中使用AutomaticKeepAliveClientMixin即可。

    class IndexedStackSlider extends StatefulWidget {
      /// Constract
      const IndexedStackSlider({
        super.key,
        required this.currentIndex,
        required this.children,
      });
    
      /// The current index
      final int currentIndex;
    
      /// The list of children
      final List<Widget> children;
    
      @override
      State<IndexedStackSlider> createState() => _IndexedStackSliderState();
    }
    
    class _IndexedStackSliderState extends State<IndexedStackSlider> {
      late PageController _pageController;
    
      @override
      void initState() {
        super.initState();
        _pageController = PageController(initialPage: widget.currentIndex);
      }
    
      @override
      void dispose() {
        _pageController.dispose();
        super.dispose();
      }
    
      @override
      void didUpdateWidget(IndexedStackSlider oldWidget) {
        super.didUpdateWidget(oldWidget);
        if (oldWidget.currentIndex != widget.currentIndex) {
          _pageController.animateToPage(
            widget.currentIndex,
            duration: const Duration(milliseconds: 300),
            curve: Curves.easeInOut,
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return PageView.builder(
          controller: _pageController,
          physics:
              const NeverScrollableScrollPhysics(), // Disable swipe between pages
          itemCount: widget.children.length,
          itemBuilder: (context, index) {
            return AnimatedBuilder(
              animation: _pageController,
              builder: (context, child) {
                return Align(
                  alignment: Alignment.topCenter,
                  child: child,
                );
              },
              child: widget.children[index],
            );
          },
        );
      }
    }
    

-1
投票

尝试将密钥添加到您的 IndexedStack 中,这样您的代码将如下所示:

body: AnimatedSwitcher(  
  duration: Duration(milliseconds: 200),  
  child: IndexedStack(
    key: ValueKey<int>(_currentIndex),
    children: _tabs.map((t) => t.widget).toList(),  
    index: _currentIndex,  
  ),  
)

密钥已更改,因此 AnimatedSwitcher 会知道它的子项需要重建。

© www.soinside.com 2019 - 2024. All rights reserved.