动画基本原理以及Flutter动画简介

一丶动画基本原理

在任何系统的UI框架中,动画实现的原理都是相同的,即:在一段时间内,快速地多次改变UI外观;由 于人眼会产生视觉暂留,所以最终看到的就是一个“连续”的动画,这和电影的原理是一样的。我们将UI 的一次改变称为一个动画帧,对应一次屏幕刷新,而决定动画流畅度的一个重要指标就是帧率 FPS(Frame Per Second),即每秒的动画帧数。很明显,帧率越高则动画就会越流畅!一般情况下, 对于人眼来说,动画帧率超过16 FPS,就基本能看了,超过 32 FPS就会感觉相对平滑,而超过 32 FPS,大多数人基本上就感受不到差别了。由于动画的每一帧都是要改变UI输出,所以在一个时间段内 连续的改变UI输出是比较耗资源的,对设备的软硬件系统要求都较高,所以在UI系统中,动画的平均帧 率是重要的性能指标,而在Flutter中,理想情况下是可以实现 60FPS 的,这和原生应用能达到的帧率 是基本是持平的。

Flutter动画简介

FLutter中的动画主要分为:隐式动画、显式动画、自定义隐式动画、自定义显式动画、和 Hero 动画。

隐式动画

通过几行代码就可以实现隐式动画,由于隐式动画背后的实现原理和繁琐的操作细节都被隐去了,所以 叫隐式动画,FLutter中提供的 AnimatedContainer、AnimatedPadding、AnimatedPositioned、 AnimatedOpacity、AnimatedDefaultTextStyle、AnimatedSwitcher都属于隐式动画。

隐式动画中可以通过duration 配置动画时长、可以通过Curve (曲线)来配置动画过程

AnimatedContainer

AnimatedContainer的属性和Container属性基本是一样的,当AnimatedContainer属性改变的时候就 会触发动画。

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),

      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body:  Center(
        child: AnimatedContainer(
          duration: const Duration(seconds: 1),
          transform: Matrix4.rotationZ(flag ? 0 : 0.25),
          width: flag ? 100 : 200,
          height: flag ? 100 : 200,
          color: Colors.red,
        ),
      ),
    );
  }
}

实现一个简单的侧边栏

// 如果需要实现遮住头部导航栏的效果,就需要使用Stack组件包裹住Scaffold组件
import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Stack(
        children: [
          ListView(
            children: const [
              ListTile(
                title: Text('我是一个列表'),
              ),
            ],
          ),
          Positioned(
            top: 0,
            left: 0,
            bottom: 0,
            child: Center(
              child: AnimatedContainer(
                duration: const Duration(seconds: 1),
                transform: flag 
                    ? Matrix4.translationValues(-200, 0, 0)
                    : Matrix4.translationValues(0, 0, 0),
                width: 200,
                height: double.infinity,

                color: Colors.red,
              ),
            ),
          )
        ],
      ),
    );
  }
}

dh

AnimatedPadding以及curve属性

Curves曲线值:

曲线名 动画过程
linear 匀速的
decelerate 匀减速
ease 开始加速,后面减速
easeIn 开始慢,后面快
easeOut 开始快,后面慢
easeInOut 开始慢,然后加速,最后再减速
fastLinearToSlowEaseIn 开始快,后面慢
bounceIn 动画结束时有一个小的反弹效果
bounceOut 动画开始时有一个小的反弹效果
bounceInOut 动画开始和结束时都有一个小的反弹效果
elasticIn 有弹性的进入
elasticOut 有弹性的退出
elasticInOut 有弹性的进入和退出
cubic 自定义曲线,需要提供一个CurveTween对象,该对象需要传入一个curve属性,该属性是一个Curve对象,Curve对象需要传入一个double类型的值,该值的范围是0-1,0表示动画开始,1表示动画结束,中间的值表示动画过程中的某个点。
更多曲线 https://docs.flutter.io/flutter/animation/Curves-class.html 官方文档打不开也可以参考教程目录中提供的gif截图

AnimatedOpacity

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
          child: AnimatedOpacity(
              duration: const Duration(seconds: 3),
              curve: Curves.elasticInOut,
              opacity: flag ? 1.0 : 0.1,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
              ))),
    );
  }
}

AnimatedDefaultTextStyle

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
          child: AnimatedOpacity(
              duration: const Duration(seconds: 3),
              curve: Curves.elasticInOut,
              opacity: flag ? 1.0 : 0.5,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.blue,
                child: AnimatedDefaultTextStyle(
                  duration: const Duration(seconds: 3),
                  curve: Curves.elasticInOut,
                  style: TextStyle(
                    fontSize: flag ? 20 : 40,
                    color: flag ? Colors.red : Colors.green,
                  ),
                  child: const Text('Hello World'),
                ),
              ))),
    );
  }
}

AnimatedSwitcher 以及transitionBuilder

上面讲的AnimatedContainer、AnimatedPadding、AnimatedPositioned、AnimatedOpacity、 AnimatedDefaultTextStyle都是在属性改变的时候执行动画,AnimatedSwitcher则是在子元素改变的 时候执行动画。

相比上面的动画组件AnimatedSwitcher多了transitionBuilder参数,可以在transitionBuilder中自定义 动画

示例1:载页面前显示一个loading动画,加载完毕后显示对于的内容
import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
          child: Container(
        alignment: Alignment.center,
        width: 200,
        height: 200,
        color: Colors.red,
        child: AnimatedSwitcher( // 当子组件发生变化时,会执行动画
          duration: const Duration(seconds: 1),
          child: flag
              ? const CircularProgressIndicator()
              : Image.network(
                  'https://www.itying.com/images/flutter/1.png',
                  fit: BoxFit.cover,
                ),
        ),
      )),
    );
  }
}
示例2:通过transitionBuilder自定义动画效果
import 'package:flutter/material.dart';
import 'package:flutter_application_1/tabs.dart';
import 'home.dart';
import 'router/routers.dart';
import 'search.dart';
import 'form.dart';
import 'news.dart';
// import 'listData.dart';

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

class MyApp extends StatelessWidget {


  // ignore: use_key_in_widget_constructors
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // home:  HomePage(), 
      // home: const Main(),
      initialRoute: '/',
      // 配置onGenerateRoute 固定写法
      onGenerateRoute: onGenerateRoute,
    );
  }
}
class Main extends StatefulWidget {

  const Main({ Key? key }) : super(key: key);

  @override
  // ignore: library_private_types_in_public_api
  _MainState createState() => _MainState();
}

class _MainState extends State<Main> {

  @override
  Widget build(BuildContext context) {
    return const TabsPage();
  }
}
// 文字需要加key,否则会报错
import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  bool flag = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
          child: Container(
        alignment: Alignment.center,
        width: 200,
        height: 200,
        color: Colors.red,
        child: AnimatedSwitcher( // 当子组件发生变化时,会执行动画
        transitionBuilder: (child, animation) {
          return ScaleTransition(
            scale: animation,
            child: child,
          );
        },
          duration: const Duration(seconds: 1),
          // child: flag
          //     ? const CircularProgressIndicator()
          //     : Image.network(
          //         'https://www.itying.com/images/flutter/1.png',
          //         fit: BoxFit.cover,
          //       ),
          child: Text(
            key: UniqueKey(),
            flag ? '我若成佛' : '天下无魔',
            style: const TextStyle(fontSize: 30),
          )
        ),
      )),
    );
  }
}

显式动画

常见的显式动画有RotationTransition、FadeTransition、ScaleTransition、SlideTransition、 AnimatedIcon。在显示动画中开发者需要创建一个AnimationController,通过AnimationController 控制动画的开始、暂停、重置、跳转、倒播等。

1、RotationTransition、 AnimationController

示例1:AnimationController普通用法

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this // 让程序和手机的刷新频率一样
        );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            RotationTransition(
              turns: _controller,
              child: const FlutterLogo(size: 200.0),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

示例2: FadeTransition lowerBound upperBound

AnimationController 用于控制动画,它包含动画的启动forward() 、停止stop() 、反向播放 reverse() 等方法。AnimationController 会在动画的每一帧,就会生成一个新的值。默认情况 下, AnimationController 在给定的时间段内线性的生成从 0.0 到1.0(默认区间)的数字 ,我们也 可以通过 lowerbound 和 upperBound 来修改AnimationController 生成数字的区间。

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
        lowerBound: 0.5,
        upperBound: 1.0
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            FadeTransition(
              // turns: _controller,
              opacity: _controller,
              child: const FlutterLogo(size: 200.0),
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

2、ScaleTransition、Tween

示例1:AnimationController控制动画

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      )..repeat(reverse: true);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ScaleTransition(
              // turns: _controller,
              // opacity: _controller,
              scale: _controller,
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              )
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

AnimationController结合Tween控制动画:

默认情况下, AnimationController 对象值的范围是[0.0,1.0]。如果我们需要构建UI的动画值在不同 的范围或不同的数据类型,则可以使用Tween 来添加映射以生成不同的范围或数据类型的值

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      )..repeat(reverse: true);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ScaleTransition(
              // turns: _controller,
              // opacity: _controller,
              scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              )
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

SlideTransition

这是一负责平移的显示动画组件,使用时需要通过position属性传入一个Animated表示位移程度,通常 借助Tween实现。

示例1: _controller.drive驱动动画

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SlideTransition(
              // turns: _controller,
              // opacity: _controller,
              // scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
              position: _controller.drive(
                Tween(begin: const Offset(0, 0), end: const Offset(1, 1))
              ),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              )
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

示例2:Tween.animate 驱动动画

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SlideTransition(
              // turns: _controller,
              // opacity: _controller,
              // scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
              position: Tween(begin: const Offset(0, 0), end: const Offset(1, 1)).animate(_controller),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              )
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

示例3: 链式操作修改动画效果

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SlideTransition(
              // turns: _controller,
              // opacity: _controller,
              // scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
              position:
              Tween(
                  begin: const Offset(0,-1),
                  end: const Offset(0,0.8) //表示实际的位置向右移动自身宽度的1.2倍
              ).chain(CurveTween(curve: Curves.bounceIn)).animate(_controller),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              )
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

示例4: 链式操作修改动动画执行时间

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SlideTransition(
              // turns: _controller,
              // opacity: _controller,
              // scale: _controller.drive(Tween(begin: 0.5, end: 1.2)),
              position:
              Tween(
                  begin: const Offset(0,-1),
                  end: const Offset(0,0.8) //表示实际的位置向右移动自身宽度的1.2倍
              ).chain(CurveTween(curve: Curves.bounceIn)).chain(CurveTween(curve: const Interval(0.0, 0.5,curve: Curves.linear))),
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              )
            ),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

AnimatedIcon

AnimatedIcon顾名思义,是一个用于提供动画图标的组件,它的名字虽然是以Animated开头,但是他 是一个显式动画组件,需要通过progress属性传入动画控制器,另外需要由Icon属性传入动画图标数据

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedIcon(icon: AnimatedIcons.menu_close,progress:
_controller,size:40),
            const SizedBox(height: 20),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: () {
                    _controller.forward(); //正序播放一次
                  },
                  child: const Text('forward'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reverse(); //倒序播放一次
                  },
                  child: const Text('reverse'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.stop(); // 停止动画
                  },
                  child: const Text('stop'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.reset(); // 重置动画
                  },
                  child: const Text('reset'),
                ),
                ElevatedButton(
                  onPressed: () {
                    _controller.repeat(); // 重复动画
                  },
                  child: const Text('repeat'),
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

交错动画

import 'package:flutter/material.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = true;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(milliseconds: 2000),
        vsync: this, // 让程序和手机的刷新频率一样
      );
      _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
            if (flag) {
              _controller.forward();
            } else {
              _controller.reverse();
            }
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Stack(
          children: [
            ScaleTransition(
              scale: _controller.drive(
                Tween(
                  begin: 0.0,
                  end: 1.0
                ).chain(CurveTween(curve: const Interval(0.5, 1.0))
                )
              ),
              child: const Icon(Icons.close, size: 40,),
            ),
            ScaleTransition(
              scale: _controller.drive(
                Tween(
                  begin: 1.0,
                  end: 0.0
                ).chain(CurveTween(curve: const Interval(0, 0.5))
                )
              ),
              child: const Icon(Icons.search, size: 40,),
            )
          ],
        ),
      ),
    );
  }
}

封装交错动画

import 'package:flutter/material.dart';

import 'tools/slidingBox.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = false;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(seconds: 6),
        vsync: this, // 让程序和手机的刷新频率一样
      );
      _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          flag ? _controller.forward() : _controller.reverse();
          flag = !flag;
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            SlidingBoxPage(
              controller: _controller,
              color: Colors.blue[200],
              curve: const Interval(0, 0.2),
            ),
            SlidingBoxPage(
              controller: _controller,
              color: Colors.blue[400],
              curve: const Interval(0.2, 0.4),
            ),
            SlidingBoxPage(
              controller: _controller,
              color: Colors.blue[600],
              curve: const Interval(0.4, 0.6),
            ),
            SlidingBoxPage(
              controller: _controller,
              color: Colors.blue[800],
              curve: const Interval(0.6, 0.8),
            ),
            SlidingBoxPage(
              controller: _controller,
              color: Colors.blue[900],
              curve: const Interval(0.8, 1),
            ),
          ],
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';

class SlidingBoxPage extends StatelessWidget {
  final AnimationController controller;
  final Color? color;
  final Curve curve;
  const SlidingBoxPage(
      {super.key, required this.controller, this.color, required this.curve});

  @override
  Widget build(BuildContext context) {
    return SlideTransition(
      position: Tween(begin: const Offset(0, 0), end: const Offset(0.3, 0))
          .chain(CurveTween(curve: Curves.bounceInOut))
          .chain(CurveTween(curve: curve))
          .animate(controller),
      child: Container(
        width: 200,
        height: 60,
        color: color,
      ),
    );
  }
}

自定义动画

import 'package:flutter/material.dart';

import 'tools/slidingBox.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = false;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(seconds: 6),
        vsync: this, // 让程序和手机的刷新频率一样
      );
      _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: TweenAnimationBuilder(
          tween: Tween(begin: 40.0, end: flag ? 100.0 : 40.0),
          duration: const Duration(seconds: 1),
          builder: (context, value, child) {
            return Icon(Icons.star, size: value, color: Colors.red,);

          },
        )
      ),
    );
  }
}

TweenAnimationBuilder自定义隐式动画

自定义透明动画

import 'package:flutter/material.dart';

import 'tools/slidingBox.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = false;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(seconds: 6),
        vsync: this, // 让程序和手机的刷新频率一样
      );
      _controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            flag = !flag;
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: TweenAnimationBuilder(
          tween: Tween(begin: 0.5, end: flag ? 0.5 : 1.0),
          duration: const Duration(seconds: 1),
          builder: (context, value, child) {
            return Opacity(opacity: value, child: const Icon(Icons.star, size: 100, color: Colors.red,));

          },
        )
      ),
    );
  }
}

TweenAnimationBuilder自定义显示动画

import 'package:flutter/material.dart';

import 'tools/slidingBox.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage>
    with SingleTickerProviderStateMixin {
  bool flag = false;
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
        duration: const Duration(seconds: 2),
        vsync: this, // 让程序和手机的刷新频率一样
      );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('搜索'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            _controller.repeat(reverse: true);
          });
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (context, child,) {
            return Opacity(
              // opacity: _controller.value, // 0.0 - 1.0
              opacity: Tween(begin: 0.5, end: 1.0).animate(_controller).value,  // 0.5 - 1.0
              child: const Icon
              (Icons.favorite, color: Colors.red, size: 100,)
            );
          },
        ),
      ),
    );
  }
}

Hero动画

Hero动画的使用

微信朋友圈点击小图片的时候会有一个动画效果到大图预览,这个动画效果就可以使用Hero 动画实现。

Hero 指的是可以在路由(页面)之间“飞行”的 widget,简单来说 Hero 动画就是在路由切换时,有一个共享的widget 可以在新旧路由间切换。

// home.dart
import 'package:flutter/material.dart';
import 'listData.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> _getListData() {
    var tempList = listData.map((e) {
      return GestureDetector(
        onTap: () {
          Navigator.pushNamed(context, '/heros', arguments: {
            'imageUrl': e['imageUrl'],
          });
        },
        child: Container(
          decoration: BoxDecoration(
            border: Border.all(
              color: Colors.black12,
              width: 1.0,
            ),
          ),
          child: Column(children: [
            Hero(tag: e['imageUrl'], child: Image.network(e['imageUrl'])),
            const SizedBox(height: 12.0),
            Text(
              e['title'],
              textAlign: TextAlign.center,
              style: const TextStyle(
                fontSize: 20.0,
                // 溢出显示省略号
                overflow: TextOverflow.ellipsis,
              ),
            ),
          ]),
        ),
      );
    });
    return tempList.toList();
  }

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      crossAxisSpacing: 10.0,
      mainAxisSpacing: 10.0,
      crossAxisCount: 2,
      padding: const EdgeInsets.all(10.0),
      children: _getListData(),
    );
  }
}
// hero.dart
import 'package:flutter/material.dart';

class HeroPage extends StatefulWidget {
  final Map arguments;
  const HeroPage({super.key, required this.arguments});

  @override
  State<HeroPage> createState() => _HeroPageState();
}

class _HeroPageState extends State<HeroPage> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        Navigator.pop(context);
      },
      child: Hero(
        tag: widget.arguments['imageUrl'],
        child: Scaffold(
          backgroundColor: Colors.black,
          body: Center(
            child: AspectRatio(
                aspectRatio: 16 / 9,
                child: Image.network(widget.arguments['imageUrl']),
          )),
      ),
    ),
    );
  }
}

配置 Hero 动画的执行时间

Hero +photo_view 实现微信朋友圈图片预览

photo_view插件支持预览图片,可放大、缩小、滑动图片

photo_view 官方地址:https://pub.dev/packages/photo_view

photo_view预览单张图片

  1. 配置依赖

    dependencies:
        photo_view: ^0.14.0
    
  1. 引入

    import 'package:photo_view/photo_view.dart';
    
  1. 单张图片预览

    import 'package:flutter/material.dart';
    import 'package:photo_view/photo_view.dart';
    
    class HeroPage extends StatefulWidget {
      final Map arguments;
      const HeroPage({super.key, required this.arguments});
    
      @override
      State<HeroPage> createState() => _HeroPageState();
    }
    
    class _HeroPageState extends State<HeroPage> {
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onTap: () {
            Navigator.pop(context);
          },
          child: Hero(
            tag: widget.arguments['imageUrl'],
            child: Scaffold(
              backgroundColor: Colors.black,
              body: Center(
                child: AspectRatio(
                    aspectRatio: 16 / 9,
                    child: PhotoView(
                      imageProvider: NetworkImage(widget.arguments['imageUrl']),
                    ),
              )),
          ),
        ),
        );
      }
    }
    

photo_view多张图片预览

  1. 配置依赖

    dependencies:
        photo_view: ^0.14.0
    
  1. 引入

    import 'package:photo_view/photo_view.dart';
    
  1. 多张图片的预览

    import 'package:flutter/material.dart';
    import 'package:flutter_application_1/listData.dart';
    import 'package:photo_view/photo_view_gallery.dart';
    import 'listData.dart';
    
    class HeroPage extends StatefulWidget {
      final Map arguments;
      const HeroPage({super.key, required this.arguments});
    
      @override
      State<HeroPage> createState() => _HeroPageState();
    }
    
    class _HeroPageState extends State<HeroPage> {
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onTap: () {
            Navigator.pop(context);
          },
          child: Hero(
            tag: widget.arguments['imageUrl'],
            child: Scaffold(
              backgroundColor: Colors.black,
              body: Center(
                child: PhotoViewGallery.builder(
                      itemCount: listData.length, 
                      builder: (BuildContext context, int index){
                        return PhotoViewGalleryPageOptions(
                          imageProvider: NetworkImage(listData[index]['imageUrl']),
                        );
                      }
                    )
              ),
          ),
        ),
        );
      }
    }
    

PhotoViewGallery.builder属性:

属性 描述
scrollPhysics BouncingScrollPhysics() 滑动到边界的时候有弹跳的效果
scrollDirection Axis.horizontal 水平 、Axis.vertical垂直方向
backgroundDecoration 背景颜色
builder builder函数根据配置的itemCount渲染函数
itemCount 数量
pageController pageController(initialPage:1)
onPageChanged onPageChanged触发的方法

更多详情参考教程以及教程源码

results matching ""

    No results matching ""