flutter SizeTransition反转动画不起作用?

新手上路,请多包涵

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

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

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;
  }
}

查询了各种方案

阅读 702
1 个回答

§§ 你的代码表达出来的思想挺好的^_^(^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

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏