动画

UI 界面设计合理的动画,可以让用户觉得更加流畅、直观,可以极大提高和改善用户体验
 
实现原理
  动画就是动起来的画面
  视觉暂留:画面经视神经传入大脑后,不会立即消失(会存留一段时间)
  帧(Frame):单个的画面,在学术上叫帧
  每秒钟展示的帧数简称 fps (Frame per Second)
 
动画分类

补间(Tween)动画
  在补间动画中,我们定义开始点和结束点、时间线以及定义转换时间和速度曲线。然后由系统计算,从开始点运动到结束点。
  从而形成动画效果。例如:透明度从 0 到 1,颜色值从 0 到 255

拟物动画
  拟物动画是对真实世界的行为进行建模,使动画效果类似于现实中的物理效果。例如:弹簧、阻尼、重力、抛物线等

动画 - Animation

  Animation,是 Flutter 动画库中的一个核心类。它包含动画的值和状态两个属性,定义了动画的一系列监听函数。

  监听值:
    addListener
    removeListener

  监听状态:
    addStatusListener
    removeStatusListener

动画状态
  AnimationStatus.dismissed -- 动画初始状态
  AnimationStatus.completed -- 动画结束状态
  AnimationStatus.forward -- 动画处在从开始到结束的运行状态
  AnimationStatus.reverse -- 动画处在从结束到开始的运行状态

 


动画 - AnimationController
  AnimationController(动画控制器)
    在指定时间内,将组件属性值由初始值演变到终止值。从而形成动画效果
 
  AnimationController 参数
    duration(动画的执行时间)
    reverseDuration(动画反向执行时间)
    lowerBound = 0.0 (动画最小值)
    upperBound = 1.0(动画最大值)
    value (动画初始值,默认是 lowerBound)
    vsync (TickerProvider 类型的对象,用来创建 Ticker 对象)
 
  当创建一个 AnimationController 时,需要传递一个 vsync 参数
    vsync 的作用是:防止屏幕外动画(动画页面切换到后台时)消耗不必要的资源
    通过将 SingleTickerProviderStateMixin 添加到类定义中,可以将 stateful 对象作为 vsync 的值
    
 
  AnimationController 具有控制动画的方法:
    .forward() 可以正向执行动画
    .reverse() 可以反向执行动画
    .dispose() 用来释放动画资源(在不使用时需要调用该方法,否则会造成资源泄漏)
    .stop() 用来停止动画运行

动画 - Tween
 
 简介
    AnimationController 动画生成值的默认区间是 0.0 到 1.0,如果希望使用不同的区间,或不同的数据类型,需要使用 Tween(补间动画)
    Tween 的唯一职责就是定义从 输入范围 到 输出范围的映射,例如:颜色区间是 0 到 255
  Tween
    Tween<double>(begin:起始值,end:终止值);
    ColorTween(begin: Colors.white, end: Colors.black);

动画 - CurvedAnimation
  简介
    动画执行的速度有多种(匀速、先快后慢或先慢后快)这里的速度称为动画曲线
    CurvedAnimation 的目的是为 AnimationController 添加动画曲线
  组件
    CurvedAnimation(parent: controller, curve: Curves.easeIn)
      parent(动画控制器对象)
      curve(正向执行的动画曲线)
      reverseCurve(反向执行的动画曲线)
    Curves
      动画曲线:https://api.flutter-io.cn/flutter/animation/Curves-class.html

动画 - 步骤
 
(1)、创建动画控制器
    controller = AnimationController(duration, vsync)
 
(2)、创建动画
    动画曲线(CurvedAnimation)
    补间动画(Tween)
 
(3)、监听动画
    addListener(); // 监听动画生成值
    addStatusListener(); // 监听动画状态
 
(4)、执行动画
    controller.forward(); // 正向执行
    controller.reverse(); // 反向执行
  
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Animation'),
      ),
      body: const AnimationDemo(),
    );
  }
}

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

  @override
  State<AnimationDemo> createState() => _AnimationDemoState();
}

class _AnimationDemoState extends State<AnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late Animation animation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    // 1、创建 AnimationController
    controller = AnimationController(
      duration: const Duration(milliseconds: 1000),
      vsync: this,
    );

    // 2-1、声明动画曲线
    animation = CurvedAnimation(parent: controller, curve: Curves.bounceIn);

    // 2-2、设置动画值的范围
    animation = Tween(begin: 50.0, end: 200.0).animate(controller);

    // 3、监听动画
    animation.addListener(() {
      setState(() {});
    });

    // 4、执行动画
    controller.forward();
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          ElevatedButton(
            onPressed: () {
              controller.forward();
            },
            child: const Text('放大'),
          ),
          ElevatedButton(
            onPressed: () {
              controller.reverse();
            },
            child: const Text('缩小'),
          ),
          ElevatedButton(
            onPressed: () {
              animation.addStatusListener((status) {
                print('status: $status');
                if (status == AnimationStatus.completed) {
                  // 反向执行动画
                  controller.reverse();
                } else if (status == AnimationStatus.dismissed) {
                  // 正向执行动画
                  controller.forward();
                }
              });
              controller.forward();
            },
            child: const Text('重复'),
          ),
          ElevatedButton(
            onPressed: () {
              controller.stop();
            },
            child: const Text('停止'),
          ),
          Icon(
            Icons.favorite,
            color: Colors.red,
            size: animation.value,
          ),
          Opacity(
            opacity: controller.value,
            child: const Text('Hello Flutter'),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    controller.dispose();
  }
}

 

交织动画
  What?
    交织动画是由多个单一动画叠加而成的复杂动画
    例如:组件变化可能涉及高度、宽度、颜色、透明度、位置等等
    需要给每个动画设置时间间隔(Interval)

  Transform(对组件进行矩阵变换)
    平移:Transform.translate()
    旋转:Transform.rotate()
    缩放:Transform.scale()
   
import 'package:flutter/material.dart';
import 'dart:math';

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Stagger Animation'),
        leading: const Icon(Icons.menu),
        actions: const [Icon(Icons.settings)],
        elevation: 0.0,
        centerTitle: true,
      ),
      body: const AnimationDemo(),
    );
  }
}

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

  @override
  State<AnimationDemo> createState() => _AnimationDemoState();
}

class _AnimationDemoState extends State<AnimationDemo>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late Animation<double> animation;
  late Animation sizeAnimation;
  late Animation colorAnimation;
  late Animation rotationAnimation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    // 1、创建 AnimationController
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 3),
    );

    // 2、创建动画
    animation = CurvedAnimation(
      parent: controller,
      curve: const Interval(0.0, 0.5),
    )..addListener(() {
        setState(() {});
      });

    // 3、让动画反复执行
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        // 反向执行动画
        controller.reverse();
      } else if (status == AnimationStatus.dismissed) {
        // 正向执行动画
        controller.forward();
      }
    });

    // 4、设置其它动画
    sizeAnimation = Tween(begin: 0.0, end: 200.0).animate(animation);
    colorAnimation = ColorTween(begin: Colors.yellow, end: Colors.red).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.5, 0.8, curve: Curves.bounceIn),
      )..addListener(() {
          setState(() {});
        }),
    );
    rotationAnimation = Tween(begin: 0.0, end: 2 * pi).animate(
      CurvedAnimation(
        parent: controller,
        curve: const Interval(0.8, 1.0, curve: Curves.easeIn),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Column(
        children: [
          ElevatedButton(
            child: const Text('重复'),
            onPressed: () {
              animation.addStatusListener((status) {
                print('status, $status');
                if (status == AnimationStatus.completed) {
                  // 反向执行动画
                  controller.reverse();
                } else if (status == AnimationStatus.dismissed) {
                  // 正向执行动画
                  controller.forward();
                }
              });
              controller.forward();
            },
          ),
          ElevatedButton(
            onPressed: () {
              controller.stop();
            },
            child: const Text('停止'),
          ),
          Icon(
            Icons.favorite,
            color: Colors.red,
            size: sizeAnimation.value,
          ),
          Opacity(
            opacity: controller.value,
            child: Transform.rotate(
              angle: rotationAnimation.value,
              child: Container(
                width: sizeAnimation.value,
                height: sizeAnimation.value,
                color: colorAnimation.value,
              ),
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    // TODO: implement dispose
    super.dispose();
    controller.dispose();
  }
}

 


Hero 动画
  Hero 动画用来实现跨页面的动画效果
    在不同页面中,声明一个共享组件(Hero)
    由于共享组件在不同页面中的位置、外观等不同,路由切换时,形成动画效果

  如何实现
    在页面A中定义起始 Hero 组件(source hero),声明 tag
    在页面B中定义 目标 Hero 组件(destination hero),绑定相同的 tag
    页面跳转时,通过 Navigator,传递 tag

  Hero 组件
    tag(路由切换时,共享组件的标记)
    child(声明子组件)
// image_detail.dart

import 'package:flutter/material.dart';

class ImageDetail extends StatelessWidget {
  final String imageUrl;
  const ImageDetail({
    Key? key,
    this.imageUrl = '',
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: GestureDetector(
          onTap: () {
            // 返回
            Navigator.pop(context);
          },
          child: Hero(
            tag: imageUrl,
            child: Image.network(
              imageUrl,
              width: double.infinity,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'image_detail.dart';

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Hero Animation'),
      ),
      body: const HeroAnimation(),
    );
  }
}

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

  @override
  State<HeroAnimation> createState() => _HeroAnimationState();
}

class _HeroAnimationState extends State<HeroAnimation> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(10),
      child: GridView.extent(
        maxCrossAxisExtent: 200.0, // 子组件最大宽度
        mainAxisSpacing: 20,
        children: List.generate(20, (index) {
          String imageUrl = 'https://picsum.photos/id/$index/300/400';
          return GestureDetector(
            onTap: () {
              Navigator.push(context,
                  MaterialPageRoute(builder: (BuildContext ctx) {
                return ImageDetail(imageUrl: imageUrl);
              }));
            },
            child: Hero(
              child: Image.network(imageUrl),
              tag: imageUrl,
            ),
          );
        }),
      ),
    );
  }
}

 

 

posted @ 2022-06-30 17:51  rogerwu  阅读(211)  评论(0编辑  收藏  举报