flutter3.0学习笔记

AnimatedSwitcher

Preview
  • AnimatedSwitcher

AnimatedSwitcher

AnimatedSwitcher动画组件来实现页面内的场景切换。AnimatedSwitcher 可以同时对其新、旧子元素添加显示、隐藏动画。也就是说在AnimatedSwitcher的子元素发生变化时,会对其旧元素和新元素做动画。

关于AnimatedSwitcher有一个地方需要特别注意,那就是如果切换的两个组件相同的话,AnimatedSwitcher会以为组件没有改变,而不会进行动效切换。如果两个组件类型相同,需要使用不同的 Key 来区分,通常是使用 ValueKey

  • 构造函数
const AnimatedSwitcher({
    super.key,
    this.child,
    required this.duration,// 新child显示动画时长
    this.reverseDuration,// 旧child隐藏的动画时长
    this.switchInCurve = Curves.linear,// 新child显示的动画曲线
    this.switchOutCurve = Curves.linear,// 旧child隐藏的动画曲线
    this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,// 动画构建器
    this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,//布局构建器
  })
  • transitionBuilder:切换转变动画构建,是一个函数,定义如下,可以用这个方法来构建自己的切换动效。
typedef AnimatedSwitcherTransitionBuilder = Widget Function(Widget child, Animation<double> animation);
  • layoutBuilder:可以设置新组件在组件树中的布局,也是一个函数。
typedef AnimatedSwitcherLayoutBuilder = Widget Function(Widget? currentChild, List<Widget> previousChildren);

例子:

import 'package:flutter/material.dart';

class AnimatedSwitcherPage extends StatefulWidget {
  const AnimatedSwitcherPage({Key? key}) : super(key: key);

  @override
  State<AnimatedSwitcherPage> createState() => _AnimatedSwitcherPageState();
}

class _AnimatedSwitcherPageState extends State<AnimatedSwitcherPage> {
  Widget? _animateWidget;
  bool isSwitcher = false;
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _animateWidget = const Image(
      image: AssetImage('images/liu.webp'),
      fit: BoxFit.cover,
      height: double.infinity,
      key: ValueKey(1),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        title: const Text('AnimatedSwitcher'),
        backgroundColor: Colors.black,
      ),
      body: Center(
        child: Container(
          padding: const EdgeInsets.all(10),
          child: AnimatedSwitcher(
            duration: const Duration(seconds: 1),
            transitionBuilder: (child,animation){
            //我们可以通过不同的动画组件实现不同的动画,下面我们用的SizeTransition组件
              return SizeTransition(
                sizeFactor: animation,
                child: child,
              );
            },
            child: _animateWidget,
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.white,
        child: const Text('点击',style: TextStyle(color: Colors.black),),
        onPressed: (){
          setState(() {
            isSwitcher = !isSwitcher;
            _animateWidget = isSwitcher
              ? const Image(
                    image: AssetImage('images/liu2.webp'),
                    fit: BoxFit.cover,
                    height: double.infinity,
                    key: ValueKey(2),
                 )
              : const Image(
                  image: AssetImage('images/liu.webp'),
                  fit: BoxFit.cover,
                  height: double.infinity,
                  key: ValueKey(1),
                );
          });
        },
      ),
    );
  }
}

效果: sq (1).gif

我们也可以自定义组件,比如自定义一个图片左出右入的动画组件:替换我们的SizeTransition组件即可

class MySlideTransition extends AnimatedWidget {
  const MySlideTransition({
    Key? key,
    required Animation<Offset> position,
    this.transformHitTests = true,
    required this.child
  }) :super(key: key, listenable:position);
  final bool transformHitTests;
  final Widget child;
  @override
  Widget build(BuildContext context) {
    final position = listenable as Animation<Offset>;
    Offset offset = position.value;
    if(position.status == AnimationStatus.reverse){
      offset = Offset(-offset.dx, offset.dy);
    }
    return FractionalTranslation(
      translation: offset,
      transformHitTests: transformHitTests,
      child: child,
    );
  }
}
transitionBuilder: (Widget child, Animation<double> animation){
  var tween = Tween<Offset>(begin: Offset(1,0),end: Offset(0,0));
  return MySlideTransition(
      position: tween.animate(animation),
      child: child
  );
},

那如果要实现“左入右出”、“上入下出”或者 “下入上出”怎么办?封装一个通用的SlideTransitionX 来实现这种“出入动画”。

class SlideTransitionX extends AnimatedWidget {
  SlideTransitionX({
    Key? key,
    required Animation<double> position,
    this.transformHitTests = true,
    this.direction = AxisDirection.down,
    required this.child,
  }) : super(key: key,listenable: position){
   switch(direction){
     case AxisDirection.up:
       _tween = Tween(begin: const Offset(0, 1),end: const Offset(0, 0));
       break;
     case AxisDirection.right:
       _tween = Tween(begin: const Offset(-1, 0),end: const Offset(0, 0));
       break;
     case AxisDirection.down:
       _tween = Tween(begin: const Offset(0, -1),end: const Offset(0, 0));
       break;
     case AxisDirection.left:
       _tween = Tween(begin: const Offset(1, 0),end: const Offset(0, 0));
       break;
   }
  }

  final bool transformHitTests;
  final AxisDirection direction;
  final Widget child;
  late final Tween<Offset> _tween;
  @override
  Widget build(BuildContext context) {
    final position = listenable as Animation<double>;

    Offset offset = _tween.evaluate(position);
    if(position.status == AnimationStatus.reverse){
      switch(direction){
        case AxisDirection.up:
          offset = Offset(offset.dx, -offset.dy);
          break;
        case AxisDirection.right:
          offset = Offset(-offset.dx, offset.dy);
          break;
        case AxisDirection.down:
          offset = Offset(offset.dx, -offset.dy);
          break;
        case AxisDirection.left:
          offset = Offset(-offset.dx, offset.dy);
          break;
      }
    }
    return FractionalTranslation(
      translation: offset,
      transformHitTests: transformHitTests,
      child: child,
    );
  }
}

使用:

 transitionBuilder: (Widget child, Animation<double> animation){
  return SlideTransitionX(
    position: animation,
    direction: AxisDirection.down,
    child: child,
  );
},