AnimatedSwitcher与IndexedStack

16

我必须使用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,因此选项卡的状态也会丢失。
7个回答

42
这是一种更干净的使用带有动画的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,  
  ),  


1
你救了我的一天!谢谢! - Hadži Lazar Pešić
1
你是我的英雄。非常出色的工作!Flutter 应该在他们的框架中实现这个!非常感谢,非常感谢。 - Fabian M
1
你真棒 - undefined

8

如果您需要使用 IndexedStack。

您可以添加自定义动画并在切换选项卡时触发它,就像这样: enter image description here

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
是的,我知道这就是它没有切换的原因。但是我需要保留选项卡的状态,而我找到的唯一方法是使用IndexedStack。你知道有没有其他替代方案可以维护选项卡的状态并且与AnimatedSwitcher兼容? - RevoltPlz
你可以在上层保存选项卡状态,并从那里传递下来。你需要存储什么类型的数据? - Kherel
大多数选项卡包含例如列表视图,我想保留滚动位置。如果我要在上层保存选项卡状态,在使用命名路由的情况下如何实现,如果我的TabPage是主路由? - RevoltPlz
持久状态和状态管理本身就是重要的主题。在您的情况下,使用indexedState可能会很好地发挥作用。 - Kherel
@Kherel非常感谢,你节省了我的时间,我们可以使用“揭示效果”吗? - DolDurma

1
尝试为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

页面转换开关( 持续时间: Duration(milliseconds: 250), 转换构建器: (widget, anim1, anim2) {

    return FadeScaleTransition(
      animation: anim1,
      child: widget,
    );
  },
  child: IndexedStack(
    index: _currentIndex,
    key: ValueKey<int>(_currentIndex),
    children: [
      Page1(),
      Page2(),
      Page3()
    ],
  ),
 
);

0
这是你可以解决这个问题的方法:
首先,使用下面的代码创建一个新的小部件。
这个小部件使用`PageView`,默认情况下会给你一个漂亮的滑动动画,但如果你想要自定义动画,也是可以的。
为了保持子元素的状态,只需在每个子元素的小部件中使用`AutomaticKeepAliveClientMixin`。
```dart class IndexedStackSlider extends StatefulWidget { /// 构造函数 const IndexedStackSlider({ super.key, required this.currentIndex, required this.children, });
/// 当前索引 final int currentIndex;
/// 子元素列表 final List children;
@override State createState() => _IndexedStackSliderState(); }
class _IndexedStackSliderState extends State { 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(), // 禁用页面之间的滑动 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], ); }, ); } } ```

0
我自己也有点失望,IndexedStack默认情况下不支持这个功能,所以我尝试着制作了一个增强版,可以支持自定义过渡效果(并且具有更多的控制权),同时仍然保持子组件的状态。
我在这里创建了一个小型测试应用的存储库: https://github.com/IMGNRY/animated_indexed_stack 欢迎提供任何反馈或者PR!

-1
尝试在IndexedStack中添加键,这样你的代码将如下所示:
body: AnimatedSwitcher(  
  duration: Duration(milliseconds: 200),  
  child: IndexedStack(
    key: ValueKey<int>(_currentIndex),
    children: _tabs.map((t) => t.widget).toList(),  
    index: _currentIndex,  
  ),  
)

关键是改变,这样AnimatedSwitcher就知道它的子项需要重建。

4
这个功能无法按预期工作,因为它在 IndexedStack 中无法保持小部件之间的状态。 - Przemek Broda

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接