带有 IndexedStack 的 AnimatedSwitcher

Posted

技术标签:

【中文标题】带有 IndexedStack 的 AnimatedSwitcher【英文标题】:AnimatedSwitcher with IndexedStack 【发布时间】:2020-01-01 18:57:58 【问题描述】:

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

【问题讨论】:

【参考方案1】:

PageTransitionSwitcher( 持续时间:持续时间(毫秒:250), transitionBuilder: (widget, anim1, anim2)

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

【讨论】:

【参考方案2】:

尝试将键添加到您的 IndexedStack 和一个 transitionBuilder 方法到您的 AnimatedSwitcher 小部件,如下所示...

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

【讨论】:

【参考方案3】:

如果您需要使用 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,
    );
  

【讨论】:

是的,我知道这就是它不切换的原因。但我需要保留选项卡的状态,而我发现这样做的唯一方法是使用 IndexedStack。您是否知道 IndexedStack 的任何替代方案,它们可以维护选项卡的状态并与 AnimatedSwitcher 兼容? 您可以在上层保存标签状态并从那里向下传递。您需要存储什么样的数据? 大多数选项卡都包含例如一个列表视图,我想为其保留滚动位置。如果我要在上层保存选项卡状态,如果我的 tabPage 是使用 namedRoutes 的主路由,我该怎么做? 持久状态和状态管理本身就是大主题。在您的情况下,使用 indexedState 可能会很好。 @Kherel 非常感谢,您节省了我的时间,我们可以用reveal effect 代替吗?【参考方案4】:

这是将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,  
  ),  

【讨论】:

你拯救了我的一天!谢谢!【参考方案5】:

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

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

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

【讨论】:

这不能按预期工作,因为它不会在 IndexedStack 中切换的小部件之间保持状态

以上是关于带有 IndexedStack 的 AnimatedSwitcher的主要内容,如果未能解决你的问题,请参考以下文章

TabBarView 或 IndexedStack 用于 BottomNavigationBar - Flutter

Flutter-IndexedStack

带有 animate.css 的角度 ngShow

仅在指定事件上带有 animate.css 的 angularjs?

带有固定元素的 jQuery .animate() 和 css 位置

动画完成后带有效果'反弹'的JQuery Animate?