【Flutter】Hero 动画 ( Hero 实现径向动画 | Hero 组件 createRectTween 设置 )(二)

简介: 【Flutter】Hero 动画 ( Hero 实现径向动画 | Hero 组件 createRectTween 设置 )(二)

四、完整代码示例


完整代码示例 :


import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'dart:math' as math;
void main() {
  runApp(MaterialApp(
    // 该组件本质是 StatelessWidget 组件子类
    home: RadialHeroAnimation(),
  ));
}
/// Hero 组件 , 跳转前后两个页面都有该组件
class ImageWidget extends StatelessWidget {
  /// 构造方法
  const ImageWidget({Key key, this.imageUrl, this.onTap}) : super(key: key);
  /// Hero 动画之间关联的 ID , 通过该标识
  /// 标识两个 Hero 组件之间进行动画过渡
  /// 同时该字符串也是图片的 url 网络地址
  final String imageUrl;
  /// 点击后的回调事件
  final VoidCallback onTap;
  @override
  Widget build(BuildContext context) {
    return Material(
      /// 获取主题颜色 , 并将透明度设置为 0.25
      color: Colors.green,
      /// 按钮
      child: InkWell(
        /// 按钮点击事件
        onTap: onTap,
        child: LayoutBuilder(
          builder: (BuildContext context, BoxConstraints size) {
            return Image.network(
              imageUrl,
              fit: BoxFit.contain,
            );
          },
        ),
      ),
    );
  }
}
/// Hero 组件 , 径向动画扩展
/// 该组件主要用于裁剪组件用的
class OvalRectWidget extends StatelessWidget {
  /// 这里的裁剪大小 clipRectSize 最大半径 / 2 的开方值 再乘以 2
  const OvalRectWidget({Key key, this.maxRadius, this.child})
      : clipRectSize = 2.0 * (maxRadius / math.sqrt2),
        super(key: key);
  // 最大半径值
  final double maxRadius;
  /// 该值需要动态计算
  final clipRectSize;
  final Widget child;
  /// 这里特别注意该圆形裁剪组件
  /// 如果整个组件的宽高都是 maxRadius ,
  /// 内部的方形组件宽高是 2.0 * (maxRadius / math.sqrt2)
  /// 并且该方形组件居中显示
  /// 那么该方形组件的四个顶点正好处于圆形组件的裁剪半径位置
  /// 也就是方形组件完整显示 , 没有裁剪到
  @override
  Widget build(BuildContext context) {
    /// 布局裁剪组件 , 可以将布局裁剪成圆形
    return ClipOval(
      /// 可用于约束布局大小的组件
      /// 这里的居中显示是关键 , 如果不居中显示 , 最终还是圆形
      child: Center(
        child: SizedBox(
          width: clipRectSize,
          height: clipRectSize,
          /// 用于裁剪圆角矩形的组件
          child: ClipRect(
            child: child,
          ),
        ),
      ),
    );
  }
}
class RadialHeroAnimation extends StatelessWidget {
  /// 最小半径
  /// 使用该半径作为组件大小时 , 组件被裁剪成圆形
  static const double minRadius = 32.0;
  /// 最大半径
  /// 使用该半径作为组件大小时 , 组件被裁剪成方形
  static const double maxRadius = 128.0;
  /// 动画差速器
  static const opacityCurve = Interval(0.0, 0.75, curve: Curves.fastOutSlowIn);
  /// 创建径向动画
  static RectTween _createRectTween(Rect begin, Rect end) {
    /// MaterialRectCenterArcTween 就是从方形到圆形变化的辅助类
    return MaterialRectCenterArcTween(begin: begin, end: end);
  }
  /// 创建页面 2 , 这是点击后跳转到的页面
  /// 三个参数分别是 : 上下文 , 图片名称 , 页面描述
  /// 页面的核心组件是 Hero 组件 , 只有 1 个
  static Widget _buildSecondPageWidget(
      BuildContext context, String imageName, String description) {
    return Container(
      color: Theme.of(context).canvasColor,
      child: Center(
        child: Card(
          /// 设置卡片布局阴影大小
          elevation: 8,
          /// 卡片布局中显示图片和图片的描述
          child: Column(
            /// 在主轴方向 , 也就是垂直方向 , 应该占用多少空间
            /// Colum 主轴方向是垂直方向
            /// Row 主轴方向是水平方向
            mainAxisSize: MainAxisSize.min,
            children: [
              SizedBox(
                /// 约束布局大小的组件的宽高定义为最大半径的两倍
                width: maxRadius * 2,
                height: maxRadius * 2,
                /// 核心 Hero 组件
                child: Hero(
                  /// 创建径向动画
                  /// 如果没有这个动画 , 中间过程会变成椭圆
                  createRectTween: _createRectTween,
                  /// Hero 动画标签 ID
                  tag: imageName,
                  /// Hero 动画作用的组件
                  child: OvalRectWidget(
                    /// 这里的半径设置为最大半径值 ,
                    maxRadius: maxRadius,
                    /// 最内层显示的是网络图片组件
                    child: ImageWidget(
                      imageUrl: imageName,
                      onTap: () {
                        /// 点击后关闭当前页面
                        Navigator.of(context).pop();
                      },
                    ),
                  ),
                ),
              ),
              /// 图片描述文本
              Text(
                // 设置文本内容
                description,
                // 设置文本样式, 粗体
                style: TextStyle(fontWeight: FontWeight.bold),
                textScaleFactor: 3.0,
              ),
              /// 空白间隔 , 无实际意义
              const SizedBox(
                height: 16,
              ),
            ],
          ),
        ),
      ),
    );
  }
  /// 创建在界面 1 显示的图标 , 点击后跳转到界面 2
  /// 页面的核心组件是 Hero 组件 , 而且是 3 个
  Widget _buildFirstPagWidget(
      BuildContext context, String imageName, String description) {
    return Container(
      /// 界面 1 中的显示的 Hero 组件是小图标
      /// 图标大小就是半径的两倍
      width: minRadius * 2.0,
      height: minRadius * 2.0,
      /// 主界面的核心 Hero 动画
      child: Hero(
        /// 这是 Hero 径向动画与标准 Hero 动画的区别
        /// 如果没有这个动画 , 中间过程会变成椭圆
        createRectTween: _createRectTween,
        /// Hero 动画标签
        tag: imageName,
        child: OvalRectWidget(
          maxRadius: maxRadius,
          /// 最内层显示的是网络图片组件
          child: ImageWidget(
            /// 设置网络图片地址
            imageUrl: imageName,
            // 设置点击事件
            onTap: () {
              /// 点击后跳转到新界面中
              Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder:
                  (BuildContext context, Animation<double> animation,
                      Animation<double> secondaryAnimation) {
                // 创建一个 RoutePageBuilder
                return AnimatedBuilder(
                    animation: animation,
                    builder: (context, child) {
                      /// 设置透明度组件
                      return Opacity(
                        /// 当前的透明度值 , 取值 0.0 ~ 1.0
                        opacity: opacityCurve.transform(animation.value),
                        // 主要显示的使用透明度控制的组件
                        // 页面 2 组件
                        child: _buildSecondPageWidget(context, imageName, description),
                      );
                    });
              }));
            },
          ),
        ),
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    /// 时间膨胀系数 , 用于降低动画运行速度
    /// 1.0 是标准速度
    timeDilation = 5.0;
    /// 主界面显示内容
    return Scaffold(
      appBar: AppBar(
        title: Text("Hero 径向动画演示"),
      ),
      body: Container(
        padding: EdgeInsets.all(32),
        alignment: FractionalOffset.bottomLeft,
        /// 横向列表显示 3 个图标
        child: Row(
          /// 排列方式 : 平分空间
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            _buildFirstPagWidget(context,
                "https://ucc.alicdn.com/images/user-upload-01/20210330094257242.png", "蜂王"),
            _buildFirstPagWidget(context,
                "https://ucc.alicdn.com/images/user-upload-01/20210330093526559.png", "蜜蜂"),
            _buildFirstPagWidget(context,
                "https://ucc.alicdn.com/images/user-upload-01/2021033009353553.png", "工蜂"),
          ],
        ),
      ),
    );
  }
}



运行效果 :


image.png






五、相关资源


参考资料 :


Flutter 官网 : https://flutter.dev/

Flutter 插件下载地址 : https://pub.dev/packages

Flutter 开发文档 : https://flutter.cn/docs ( 强烈推荐 )

官方 GitHub 地址 : https://github.com/flutter

Flutter 中文社区 : https://flutter.cn/

Flutter 实用教程 : https://flutter.cn/docs/cookbook

Flutter CodeLab : https://codelabs.flutter-io.cn/

Dart 中文文档 : https://dart.cn/

Dart 开发者官网 : https://api.dart.dev/

Flutter 中文网 : https://flutterchina.club/ , http://flutter.axuer.com/docs/

Flutter 相关问题 : https://flutterchina.club/faq/ ( 入门阶段推荐看一遍 )

GitHub 上的 Flutter 开源示例 : https://download.csdn.net/download/han1202012/15989510

Flutter 实战电子书 : https://book.flutterchina.club/chapter1/


重要的专题 :


Flutter 动画参考文档 : https://flutterchina.club/animations/


博客源码下载 :


GitHub 地址 : https://github.com/han1202012/flutter_animation ( 随博客进度一直更新 , 有可能没有本博客的源码 )


博客源码快照 : https://download.csdn.net/download/han1202012/16245277 ( 本篇博客的源码快照 , 可以找到本博客的源码 )


目录
相关文章
|
4月前
Flutter 组件(二)文本 与 输入框组件
Flutter 组件(二)文本 与 输入框组件
74 0
|
4月前
|
容器
Flutter 组件(一)组件概述
Flutter 组件(一)组件概述
58 0
|
3天前
|
前端开发 搜索推荐 UED
【Flutter前端技术开发专栏】Flutter中的高级UI组件应用
【4月更文挑战第30天】探索Flutter的高级UI组件,如`TabBar`、`Drawer`、`BottomSheet`,提升应用体验和美观度。使用高级组件能节省开发时间,提供内置交互逻辑和优秀视觉效果。示例代码展示了如何实现底部导航栏、侧边导航和底部弹出菜单。同时,自定义组件允许个性化设计和功能扩展,但也带来性能优化和维护挑战。参考Flutter官方文档和教程,深入学习并有效利用这些组件。
【Flutter前端技术开发专栏】Flutter中的高级UI组件应用
|
7天前
|
开发框架 API 开发者
Flutter的动画:实现方式与动画库的技术探索
【4月更文挑战第26天】探索Flutter动画机制与库:基础动画、自定义动画、物理动画及Lottie、AnimatedWidgets、EasyAnimations等库的应用,助开发者实现丰富动画效果,提升用户体验。同时,了解性能优化技巧,如避免重绘、利用离屏渲染和GPU加速,确保动画流畅。 Flutter为移动应用开发带来强大动画支持。
|
8天前
|
Dart
Flutter 中优雅切换应用主题的组件
【Flutter】使用adaptive_theme组件优雅切换应用主题:封装MaterialApp,支持light/dark/system模式,自定义色彩,保存选择,含调试按钮。安装配置、设置主题、监听切换、自定义颜色、读取配置步骤详细。代码示例与学习路径推荐。[查看完整教程](https://flutter.ducafecat.com/blog/flutter-app-theme-switch)
Flutter 中优雅切换应用主题的组件
|
8月前
|
人机交互
Flutter笔记 - ListTile组件及其应用
Flutter笔记 - ListTile组件及其应用
97 0
|
4月前
|
前端开发
Flutter笔记:光影动画按钮、滚动图标卡片组等
Flutter笔记:光影动画按钮、滚动图标卡片组等
40 0
|
4月前
|
开发者 索引 容器
Flutter开发笔记:Flutter 布局相关组件
Flutter开发笔记:Flutter 布局相关组件
125 0
|
4月前
|
iOS开发
Flutter 组件(三)按钮类组件
Flutter 组件(三)按钮类组件
157 0
|
10月前
|
缓存 Dart 前端开发
Flutter 中使用 Widgetbook 管理你的组件 | 猫哥
Flutter 界面开发中我们有几个痛点 : - 与设计师协作复用一套设计规范(figma) - 可视化的管理你的组件代码(基础组件、业务组件) - 不同设备尺寸测试你的组件 - 实时修改你的测试组件参数
4212 1
Flutter 中使用 Widgetbook 管理你的组件 | 猫哥