动画-3 Hero动画

如何使用Hero动画?

什么是Hero动画?
[Flutter Hero动画开发实用教程] (https://cloud.tencent.com/developer/article/1551853)

在 Flutter中可以用 Hero widget创建这个动画。当 hero 通过动画从源页面飞到目标页面时,目标页面逐渐淡入视野。通常, hero 是用户界面的一小部分,如图片,它通常在两个页面都有。从用户的角度来看, hero 在页面之间“飞翔”。接下来我们一起来学习如何创建Hero动画:

实现标准hero动画

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
 
class PhotoHero extends StatelessWidget {
  const PhotoHero({Key? key, required this.photo, required this.onTap, required this.width }) : super(key: key);
 
  final String photo;
  final VoidCallback onTap; // 点击回调
  final double width;
 
  Widget build(BuildContext context) {
    return SizedBox(
      width: width,
      child: Hero(
        tag: photo, // 动画关联,关联2个动画。
        child: Material(
          color: Colors.transparent,
          child: InkWell(
            onTap: onTap,
            child: Image.network(photo, fit: BoxFit.contain),
          ),
        ),
      ),
    );
  }
}
class HeroAnimation extends StatelessWidget {
  const HeroAnimation({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    timeDilation = 10.0; // 1.0 means normal animation speed.
    String url = 'https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2022%2F1109%2F59f0e407j00rl2ziu00a2c001au00v8g.jpg&thumbnail=660x2147483647&quality=80&type=jpg';

    return Scaffold(
      appBar: AppBar(
        title: const Text('Basic Hero Animation'),
      ),
      body: Center(
        child: PhotoHero(
          photo: url,
          width: 300.0,
          onTap: () {
            // 点击页面跳转。
            Navigator.of(context).push(MaterialPageRoute<void>(
                builder: (BuildContext context) {
                  return Scaffold(
                    appBar: AppBar(
                      title: const Text('Flippers Page'),
                    ),
                    body: Container(
                      // Set background to blue to emphasize that it's a new route.
                      color: Colors.lightBlueAccent,
                      padding: const EdgeInsets.all(16.0),
                      alignment: Alignment.topLeft,
                      child: PhotoHero(
                        photo: url,
                        width: 100.0,
                        onTap: () {
                          // 跳出当前页面
                          Navigator.of(context).pop();
                        }, key: null,
                      ),
                    ),
                  );
                }
            ));
          }, key: null,
        ),
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(home: HeroAnimation()));
}

Hero的函数原型

 const Hero({
    Key key,
    @required this.tag,
    this.createRectTween,
    this.flightShuttleBuilder,
    this.placeholderBuilder,
    this.transitionOnUserGestures = false,
    @required this.child,
  }) : assert(tag != null),
       assert(transitionOnUserGestures != null),
       assert(child != null),
       super(key: key);
  • tag:[必须]用于关联两个Hero动画的标识;
  • createRectTween:[可选]定义目标Hero的边界,在从起始位置到目的位置的“飞行”过程中该如何变化;
  • child:[必须]定义动画所呈现的widget;

实现径向hero动画

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
 
class Photo extends StatelessWidget {
  const Photo({ Key? key, required this.photo, required this.color, required this.onTap }) : super(key: key);
 
  final String photo;
  final Color? color;
  final VoidCallback onTap;
 
  @override
  Widget build(BuildContext context) {
    return Material(
      // Slightly opaque color appears where the image has transparency.
      color: Theme.of(context).primaryColor.withOpacity(0.25),
      child: InkWell(
        onTap: onTap,
        child: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints size) {
            return Image.network( photo, fit: BoxFit.contain);
          },
        ),
      ),
    );
  }
}
class RadialExpansion extends StatelessWidget {
  const RadialExpansion({
    Key? key,
    required this.maxRadius,
    required this.child,
  }) : clipRectSize = 2.0 * (maxRadius / math.sqrt2),
        super(key: key);
 
  final double maxRadius;
  final double clipRectSize;
  final Widget child;
 
  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Center(
        child: SizedBox(
          width: clipRectSize,
          height: clipRectSize,
          child: ClipRect(
            child: child,
          ),
        ),
      ),
    );
  }
}
class RadialExpansionDemo extends StatelessWidget {
  static const double kMinRadius = 32.0;
  static const double kMaxRadius = 128.0;
  static const opacityCurve = Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);
 
  const RadialExpansionDemo({Key? key}) : super(key: key);
 
  static RectTween _createRectTween(Rect? begin, Rect? end) {
    // 方形到原型变化的辅助类。
    return MaterialRectCenterArcTween(begin: begin, end: end);
  }
 
  static Widget _buildPage(BuildContext context, String imageName, String description) {
    return Container(
      color: Theme.of(context).canvasColor,
      child: Center(
        child: Card(
          elevation: 8.0,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                width: kMaxRadius * 2.0,
                height: kMaxRadius * 2.0,
                child: Hero(
                  createRectTween: _createRectTween,
                  tag: imageName,
                  child: RadialExpansion(
                    maxRadius: kMaxRadius,
                    child: Photo(
                      photo: imageName,
                      onTap: () {
                        // 子页面图片点击,关闭页面。动画回前页面。
                        Navigator.of(context).pop();
                      }, color: null,
                    ),
                  ),
                ),
              ),
              Text(
                description,
                style: const TextStyle(fontWeight: FontWeight.bold),
                textScaleFactor: 3.0,
              ),
              const SizedBox(height: 16.0),
            ],
          ),
        ),
      ),
    );
  }
 
  // 构建主页面的3个item
  Widget _buildHero(BuildContext context, String imageName, String description) {
    return SizedBox(
      width: kMinRadius * 2.0,
      height: kMinRadius * 2.0,
      child: Hero(
        createRectTween: _createRectTween, // 动画转换的形状效果。
        tag: imageName,
        child: RadialExpansion(
          maxRadius: kMaxRadius,
          child: Photo(
            photo: imageName,
            onTap: () {
              // 点击底部item,进入图片详情页面,PageRouteBuilder带淡入淡出效果。
              Navigator.of(context).push(
                PageRouteBuilder<void>(
                  pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
                    return AnimatedBuilder(
                        animation: animation,
                        builder:(BuildContext context, Widget? child) {
                          return Opacity(
                            // 改变透明度
                            opacity: opacityCurve.transform(animation.value),
                            child: _buildPage(context, imageName, description),
                          );
                        }
                    );
                  },
                ),
              );
            }, color: null,
          ),
        ),
      ),
    );
  }
 
  @override
  Widget build(BuildContext context) {
    timeDilation = 5.0; // 1.0 is normal animation speed.
 
    return Scaffold(
      appBar: AppBar(
        title: const Text('Radial Transition Demo'),
      ),
      body: Container(
        padding: const EdgeInsets.all(32.0),
        alignment: FractionalOffset.bottomLeft, // 底部放3个图片。
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            _buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/chair-alpha.png', 'Chair'),
            _buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/binoculars-alpha.png', 'Binoculars'),
            _buildHero(context, 'https://raw.githubusercontent.com/flutter/website/master/examples/_animation/radial_hero_animation/images/beachball-alpha.png', 'Beach ball'),
          ],
        ),
      ),
    );
  }
}
 
void main() {
  runApp(const MaterialApp(home: RadialExpansionDemo()));
}
  • Animation:是Flutter动画库中的一个核心类,它生成指导动画的值;

  • CurvedAnimation:Animation的一个子类,将过程抽象为一个非线性曲线;

  • AnimationController:Animation的一个子类,用来管理Animation;

  • Tween:在正在执行动画的对象所使用的数据范围之间生成值。例如,Tween可生成从红到蓝之间的色值,或者从0到255;

    Animation还可以生成除double之外的其他类型值,如:Animation<Color> 或 Animation<Size>;
    Animation对象有状态。可以通过访问其value属性获取动画的当前值;
    Animation对象本身和UI渲染没有任何关系;

    forward():启动动画;
    reverse({double from}):倒放动画;
    reset():重置动画,将其设置到动画的开始位置;
    stop({ bool canceled = true }):停止动画;

    addListener:动画的值发生变化时被调用;
    addStatusListener:动画状态发生变化时被调用;

    显示logo
    定义Animation对象
    渲染过渡效果

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容