当前位置: 首页 > 知识库问答 >
问题:

flutter SizeTransition反转动画不起作用?

陶树
2024-08-16

不透明度在打开和关闭时都有效,高度变化仅在打开时有动画效果,在关闭时没有动画效果。

下面是我的代码,但一直没有成功。

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: MyHomePage(),
    ));
  }
}

class MyHomePage extends StatelessWidget {
  void _showDismissibleDialog(BuildContext context) async {
    Navigator.of(context).push(DismissibleDialog());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Dismissible Dialog Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => _showDismissibleDialog(context),
          child: Text('Show Dialog'),
        ),
      ),
    );
  }
}

class DismissibleDialog<T> extends PopupRoute<T> {
  @override
  Color? get barrierColor => Colors.transparent;

  @override
  bool get barrierDismissible => true;

  @override
  String? get barrierLabel => 'Dismissible Dialog';

  @override
  Duration get transitionDuration => const Duration(milliseconds: 300);

  @override
  bool get opaque => false;

  @override
  Widget buildModalBarrier() {
    return const AnimatiedContainer(
      child: BuildBarrier(),
    );
  }

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return const AnimatiedContainer(
      child: BuildPage(),
    );
  }
}

class BuildBarrier extends StatelessWidget {
  const BuildBarrier({super.key});

  @override
  Widget build(BuildContext context) {
    final opacityController =
        AnimationControllerProvider.of(context).opacityController;
    final heightController =
        AnimationControllerProvider.of(context).heightController;

    return SafeArea(
      child: Stack(
        children: [
          Positioned(
            top: kToolbarHeight,
            left: 0,
            right: 0,
            bottom: 0,
            child: GestureDetector(
              onTap: () {
                Future.wait([
                  opacityController.reverse(),
                  heightController.reverse(),
                ]).then((res) {
                  Navigator.of(context).pop();
                });
              },
              child: FadeTransition(
                opacity: Tween<double>(begin: 0, end: 0.5).animate(
                  CurvedAnimation(
                      parent: opacityController, curve: Curves.easeInOut),
                ),
                child: Container(
                  color: Colors.black.withOpacity(0.5),
                ),
              ),
            ),
          )
        ],
      ),
    );
  }
}

class BuildPage extends StatelessWidget {
  const BuildPage({super.key});

  @override
  Widget build(BuildContext context) {
    final heightController =
        AnimationControllerProvider.of(context).heightController;

    return SafeArea(
      child: DefaultTextStyle(
        style: Theme.of(context).textTheme.bodyMedium!,
        child: Stack(
          children: [
            Positioned(
              top: kToolbarHeight,
              left: 0,
              child: SizeTransition(
                sizeFactor: Tween<double>(begin: 0, end: 1).animate(
                  CurvedAnimation(
                      parent: heightController, curve: Curves.easeInOut),
                ),
                axisAlignment: -1,
                child: Container(
                  width: MediaQuery.of(context).size.width,
                  padding: const EdgeInsets.all(20.0),
                  decoration: const BoxDecoration(
                    color: Colors.white,
                  ),
                  child: Column(
                    children: <Widget>[
                      Text(
                        'Dismissible Dialog',
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class AnimatiedContainer extends StatefulWidget {
  final Widget child;

  const AnimatiedContainer({super.key, required this.child});

  @override
  State<AnimatiedContainer> createState() => _AnimatiedContainerState();
}

class _AnimatiedContainerState extends State<AnimatiedContainer>
    with TickerProviderStateMixin {
  late final AnimationController _opacityController;
  late final AnimationController _heightController;

  bool isExpand = false;

  @override
  void initState() {
    super.initState();

    _heightController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    )..forward();

    _opacityController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    )..forward();
  }

  @override
  void dispose() {
    _opacityController.dispose();
    _heightController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimationControllerProvider(
      opacityController: _opacityController,
      heightController: _heightController,
      isExpand: isExpand,
      child: Container(
        child: widget.child,
      ),
    );
  }
}

class AnimationControllerProvider extends InheritedWidget {
  final AnimationController opacityController;
  final AnimationController heightController;
  final bool isExpand;

  const AnimationControllerProvider({
    Key? key,
    required this.opacityController,
    required this.heightController,
    required Widget child,
    this.isExpand = false,
  }) : super(key: key, child: child);

  static AnimationControllerProvider of(BuildContext context) {
    final AnimationControllerProvider? result = context
        .dependOnInheritedWidgetOfExactType<AnimationControllerProvider>();
    assert(result != null, 'No AnimationController found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(AnimationControllerProvider old) {
    return opacityController != old.opacityController ||
        heightController != old.heightController;
  }
}

查询了各种方案

共有1个答案

唐恺
2024-08-16

§§ 你的代码表达出来的思想挺好的^_^(^o^)/~ §§

把BuildBarrier中的GestureDetector放到BuildPage中来,另外AnimationController 有一个addStatusListener的方法,可以观察动画执行的状态;

GestureDetector(
            onTap: () {
              Future.wait([
                heightController.reverse(),
                opacityController.reverse(),
              ]).then((res) {
                Navigator.of(context).pop();
              });
            },

下面是完整的修改过后的代码:

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      body: MyHomePage(),
    ));
  }
}

class MyHomePage extends StatelessWidget {
  void _showDismissibleDialog(BuildContext context) async {
    Navigator.of(context).push(DismissibleDialog());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Dismissible Dialog Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => _showDismissibleDialog(context),
          child: Text('Show Dialog'),
        ),
      ),
    );
  }
}

class DismissibleDialog<T> extends PopupRoute<T> {
  @override
  Color? get barrierColor => Colors.transparent;

  @override
  bool get barrierDismissible => true;

  @override
  String? get barrierLabel => 'Dismissible Dialog';

  @override
  Duration get transitionDuration => const Duration(milliseconds: 300);

  @override
  bool get opaque => false;

  @override
  Widget buildModalBarrier() {
    return const AnimatiedContainer(
      child: BuildBarrier(),
    );
  }

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return const AnimatiedContainer(
      child: BuildPage(),
    );
  }
}

class BuildBarrier extends StatelessWidget {
  const BuildBarrier({super.key});

  @override
  Widget build(BuildContext context) {
    final opacityController =
        AnimationControllerProvider.of(context).opacityController;
    return SafeArea(
      child: Stack(
        children: [
          Positioned(
            top: kToolbarHeight,
            left: 0,
            right: 0,
            bottom: 0,
            child: FadeTransition(
              opacity: Tween<double>(begin: 0, end: 0.5).animate(
                CurvedAnimation(
                    parent: opacityController, curve: Curves.easeInOut),
              ),
              child: Container(
                color: Colors.black.withOpacity(0.5),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class BuildPage extends StatelessWidget {
  const BuildPage({super.key});

  @override
  Widget build(BuildContext context) {
    final heightController =
        AnimationControllerProvider.of(context).heightController;
    final opacityController =
        AnimationControllerProvider.of(context).opacityController;
    return SafeArea(
      child: DefaultTextStyle(
          style: Theme.of(context).textTheme.bodyMedium!,
          child: GestureDetector(
            onTap: () {
              Future.wait([
                heightController.reverse(),
                opacityController.reverse(),
              ]).then((res) {
                Navigator.of(context).pop();
              });
            },
            child: Stack(
              children: [
                Positioned(
                  top: kToolbarHeight,
                  left: 0,
                  child: SizeTransition(
                    sizeFactor: Tween<double>(begin: 0, end: 1).animate(
                      CurvedAnimation(
                          parent: heightController, curve: Curves.easeInOut),
                    ),
                    axisAlignment: -1,
                    child: Container(
                      width: MediaQuery.of(context).size.width,
                      padding: const EdgeInsets.all(20.0),
                      decoration: const BoxDecoration(
                        color: Colors.white,
                      ),
                      child: Column(
                        children: <Widget>[
                          Text(
                            'Dismissible Dialog ----',
                            style: Theme.of(context).textTheme.headlineSmall,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
          )),
    );
  }
}

class AnimatiedContainer extends StatefulWidget {
  final Widget child;

  const AnimatiedContainer({super.key, required this.child});

  @override
  State<AnimatiedContainer> createState() => _AnimatiedContainerState();
}

class _AnimatiedContainerState extends State<AnimatiedContainer>
    with TickerProviderStateMixin {
  late final AnimationController _opacityController;
  late final AnimationController _heightController;

  bool isExpand = false;

  @override
  void initState() {
    super.initState();

    _heightController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    )
      ..forward()
      //这里可以观察状态。
      ..addStatusListener((state) {
        print(state);
      });

    _opacityController = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    )..forward();
  }

  @override
  void dispose() {
    _opacityController.dispose();
    _heightController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimationControllerProvider(
      opacityController: _opacityController,
      heightController: _heightController,
      isExpand: isExpand,
      child: Container(
        child: widget.child,
      ),
    );
  }
}

class AnimationControllerProvider extends InheritedWidget {
  final AnimationController opacityController;
  final AnimationController heightController;
  final bool isExpand;

  const AnimationControllerProvider({
    Key? key,
    required this.opacityController,
    required this.heightController,
    required Widget child,
    this.isExpand = false,
  }) : super(key: key, child: child);

  static AnimationControllerProvider of(BuildContext context) {
    final AnimationControllerProvider? result = context
        .dependOnInheritedWidgetOfExactType<AnimationControllerProvider>();
    assert(result != null, 'No AnimationController found in context');
    return result!;
  }

  @override
  bool updateShouldNotify(AnimationControllerProvider old) {
    return opacityController != old.opacityController ||
        heightController != old.heightController;
  }
}
也许类似下面的写法会更好,重点就是不重写BuildBarrier,而是利用系统提供的API,这样就可以不用写controller啦。
void main() {
  runApp(const MyApp());
}

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

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  void _showDismissibleDialog(BuildContext context) async {
    Navigator.of(context).push(DismissibleDialog());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dismissible Dialog Example'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () => _showDismissibleDialog(context),
          child: const Text('Show Dialog'),
        ),
      ),
    );
  }
}

class DismissibleDialog<T> extends PopupRoute<T> {
  //不重写BuildBarrier法方,所以在这里就可以使用api提供的这些属性设置默认的barriy啦

  @override
  Color? get barrierColor => Colors.blue; //设置背景色

  /*
  下面这个barrierDismissible的介绍也很重要,鼠标移上去就可以看到
  * If barrierDismissible is true, then tapping this barrier, pressing the escape key on the keyboard, or calling route popping functions such as Navigator. pop will cause the current route to be popped with null as the value.
  * If barrierDismissible is false, then tapping the barrier has no effect.
* */
  @override
  bool get barrierDismissible => false; //是否Dismissble

  @override
  String? get barrierLabel => null;

  @override
  Duration get transitionDuration =>
      const Duration(milliseconds: 500); //当push 或者 pop时候buildPage方法中动画执行的时间

  @override
  bool get opaque => false;

  //利用提供的APi中的Animation;就可以不用多写啦。这可能也是这个api提供者所考虑到的
  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return SlideTransition(
        position: Tween(begin: const Offset(0, 1), end: const Offset(0, 0))
            .animate(animation),
        child: FractionallySizedBox(
          heightFactor: 0.6,
          child: Container(
            alignment: Alignment.center,
            color: Colors.pink,
            child: Center(
              child: TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: const BuildPage1(),
              ),
            ),
          ),
        ));
  }
}

class BuildPage1 extends StatelessWidget {
  const BuildPage1({super.key});
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: DefaultTextStyle(
          style: Theme.of(context).textTheme.bodyMedium!,
          child: Center(
            child: TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                },
                child: const Text('Touch Me')),
          )),
    );
  }
}

( ̄o ̄) . z Z

 类似资料:
  • 我正在工作的css动画过渡。我有我的div id mybus。它的位置是相对的。它是多个divs位置相对的容器。我正在尝试移动mybus,它是所有其他div的容器,使用动画转换:translateX()。不是沃金。(问题是,动画时位置重要吗?)我单独地将transform:rotate()动画应用到div类车轮(它也在mybus内部)中,效果很好。(我没有包含这部分代码)如果需要,我可以给出更多关

  • 问题内容: 我正在尝试使形状的动画随着样本大小的增加(从100增加到1000)而呈指数分布。还有一个滑块,用于配置发行版的lambda参数,以及一个用于启动动画的按钮。 我的问题是,即使布局得到渲染,当我按下开始按钮时也没有动画发生。到目前为止,这是我的尝试: 为了简化起见,我在Python笔记本中运行它: 问题答案: 您应该注意函数的名称空间。 考虑 因此,您需要处理实际对象,而不是它们的某些本

  • 我有一个由< code > RealmRecyclerViewAdapter 填充的< code > recycle view ,但不知何故,当数据更改时,没有动画播放。adapter类为不同的布局使用多个< code > view holder ,但这不应该影响动画,对吗? 用于设置 和适配器的代码如下所示: 将数据绑定到适配器的代码如下所述: 自动更新工作正常,但不知何故没有数据更改时没有动画

  • 问题内容: 我有一个按钮,该按钮调用animateWithDuration代码,以淡出图像,淡入文本和in的新bg颜色,然后将其重置为正常值。动画需要几秒钟才能完成,效果很好。 然而! 有一个问题: 有时在动画结束之前会再次按下此按钮。发生这种情况时,我希望当前的动画停止并重新开始。 研究解决方案不起作用 根据我的阅读,解决方案应该很简单,只需导入QuartzCore并添加: 这确实删除了动画,但

  • 该项目提供了两种动画变体。 动画选项1,触发器('imationOption1') 无投诉。 动画选项2,触发器('imationOption2') 转换在此处不起作用。 在线查看这个项目在StackBlitz.com app.component.html 应用程序组件.ts 谷歌搜索并没有导致解决方案。

  • 下面的函数只是将视图移动到一个新位置。它不显示动画: