Flutter Hero 实现径向变换动画 — 圆形变成矩形的转场动画

系列文章

  1. Flutter 旋转动画 — RotationTransition
  2. Flutter 平移动画 — 4种实现方式
  3. Flutter 淡入淡出与逐渐出现动画
  4. Flutter 尺寸缩放、形状、颜色、阴影变换动画
  5. Flutter 列表Item动画 — AnimatedList实现Item左进左出、淡入淡出
  6. Flutter Hero 实现共享元素转场动画
  7. Flutter Hero 实现径向变换动画 — 圆形变成矩形的转场动画
  8. Flutter 自定义动画 — 数字递增动画和文字逐行逐字出现或消失动画

以下大部分内容源于官方文档及Demo
文档:https://docs.flutter.dev/development/ui/animations/hero-animations#radial-hero-animations
Demo:


1 动画效果图

在这里插入图片描述


2 Radial transformation(径向变换)动画

官方介绍:https://material.io/guidelines/motion/transforming-material.html
意思触摸圆形,然后圆形变换为其它形状的一种动画效果。
建议的2种展示方式:

在这里插入图片描述
在这里插入图片描述


3 Hero 实现圆形变成矩形的转场动画

Hero 是Flutter提供的一个可以实现子Widget在页面切换时带有飞行效果的Widget,一般用于图片。
可看博客:Flutter Hero 实现共享元素转场动画

Radial transformation 径向变换动画效果一般用于圆形变矩形。

3.1 实现原理

实现变换圆形变矩形的转场动画原理(来源于官方文档)。

在这里插入图片描述
蓝色渐变代表图像,表示剪辑形状相交的位置。
在动画开始前,相交的结果是一个圆形剪辑 ( ClipOval)。
在动画执行过程中,ClipRect保持恒定大小,ClipOval开始缩放。
在动画结束时,圆形和矩形剪辑的交点产生一个与Hero Widget相同大小的矩形。即图像不再被剪裁。

裁剪Widget的代码实现

import 'dart:math' as math;

import 'package:flutter/material.dart';

class RadialExpansionWidget extends StatelessWidget {
  const RadialExpansionWidget({
    super.key,
    required this.maxRadius,
    this.child,
  }) : clipRectSize = 2.0 * (maxRadius / math.sqrt2);

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

3.2 代码实现圆形变矩形动画

自定义的RadialExpansionWidget实现裁剪形状,使用Hero 实现飞行效果。

第一页展示4个半径为30的圆

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

  @override
  Widget build(BuildContext context) {
    // import 'package:flutter/scheduler.dart' show timeDilation;
    // 使转场速度变慢,便于观察转场动画形状的变化过程
    timeDilation = 3.0;

    return Scaffold(
      appBar: AppBar(title: const Text('FirstPage')),
      body: Align(
        alignment: Alignment.bottomCenter,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: List.generate(4, (index) => _buildItem(context, index)),
        ),
      ),
    );
  }

  Widget _buildItem(BuildContext context, int index) {
    return CupertinoButton(
      child: _buildHeroWidget(index),
      onPressed: () {
        Navigator.of(context).push(
          PageRouteBuilder<void>(
            pageBuilder: (context, animation, secondaryAnimation) {
              return SecondPage(index: index);
            },
          ),
        );
      },
    );
  }

  ///目标实现半径 30的圆,转换为半径120的圆包裹的矩形
  Widget _buildHeroWidget(int index) {
    const radius = 30;
    return SizedBox(
      width: radius * 2,
      height: radius * 2,
      child: Hero(
        tag: 'hero_tag_$index',
        child: RadialExpansionWidget(
          maxRadius: 120,
          child: Container(
            color: Colors.red,
            child: LayoutBuilder(
              builder: (context, constraints) {
                return FlutterLogo(size: constraints.maxWidth);
              },
            ),
          ),
        ),
      ),
    );
  }
}

第二页展示一个卡片,圆形的图变成了矩形的图。点击页面内容回到上一页。

class SecondPage extends StatelessWidget {
  final int index;

  const SecondPage({Key? key, required this.index}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // 图片矩形是由半径为120的圆得来
    const maxRadius = 120.0;
    return GestureDetector(
      onTap: () => Navigator.of(context).pop(),
      child: Container(
        color: Theme.of(context).canvasColor,
        height: double.infinity,
        width: double.infinity,
        alignment: Alignment.center,
        child: Card(
          elevation: 8.0,
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                width: maxRadius * 2,
                height: maxRadius * 2,
                child: Hero(
                  tag: 'hero_tag_$index',
                  child: RadialExpansionWidget(
                    maxRadius: maxRadius,
                    child: Container(
                      color: Colors.red,
                      child: LayoutBuilder(
                        builder: (context, constraints) {
                          return FlutterLogo(size: constraints.maxWidth);
                        },
                      ),
                    ),
                  ),
                ),
              ),
              Text(
                '第$index个Item',
                style: const TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 16.0),
            ],
          ),
        ),
      ),
    );
  }
}

此时的动画效果
在这里插入图片描述
动画的形状变成了椭圆,Hero在MaterialApp中默认使用MaterialRectArcTween,要实现动画过程中裁剪形状为圆形,需要使用MaterialRectCenterArcTween

在使用了Hero的地方修改Hero的路径动画

    Hero(
        tag: 'hero_tag_$index',
        createRectTween: (begin, end) {
          return MaterialRectCenterArcTween(begin: begin, end: end);
        },
        child: ...
    )

此时的效果
在这里插入图片描述


3.3 添加背景透明度动画让转场动画更自然

Navigator.of(context).push() PageRouteBuilder中,添加透明度动画,让页面切换更自然。

  Widget _buildItem(BuildContext context, int index) {
    return CupertinoButton(
      child: _buildHeroWidget(index),
      onPressed: () {
        Navigator.of(context).push(
          PageRouteBuilder<void>(
            pageBuilder: (context, animation, secondaryAnimation) {
              // 透明度变换Widget
              return FadeTransition(
                opacity: CurvedAnimation(
                  parent: animation,
                  curve: Curves.fastOutSlowIn, // 非曲线动画,慢进快出
                ),
                child: SecondPage(index: index),
              );
            },
          ),
        );
      },
    );
  }

最终动画效果
在这里插入图片描述
文中的代码基本是参考的官方DEMO:
https://github.com/flutter/website/tree/main/examples/_animation/radial_hero_animation

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值