【Flutter】Hero 动画 ( Hero 实现径向动画 | Hero 组件 createRectTween 设置 )
文章目錄
- ?、Hero 構造函數
- 一、圓形方形組件
- 二、創建頁面 1 的組件 ( Hero 組件 1 )
- 三、創建頁面 2 的組件 ( Hero 組件 2 )
- 四、完整代碼示例
- 五、相關資源
?、Hero 構造函數
Hero 構造函數 :
/// 創建一個 Hero 組件 ;////// tag , child 參數不能為空 ; /// child 參數的值不能是 Hero 組件以及 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);required this.tag : 不能為空 , 用于 關聯兩個界面的 Hero 組件 , 兩個 Hero 組件有關聯關系 , 則設置相同的 tag 字符串 ;
this.createRectTween : 可以為空 , 用于 定義 Hero 組件的邊界 , 以及定義 Hero 組件在界面切換時 , 從 源界面的起始位置 到 目的界面的最終位置 , 動畫執行的變化過程 ;
required this.child : 不能為空 , 普通的 Widget 組件 , Hero 動畫作用的組件 ;
Hero 動畫可以實現徑向動畫 , 徑向動畫指的是組件形狀可變的動畫 , 如圓形變方形 , 方形變三角形 ;
Hero 徑向動畫 與 普通動畫的區別就是是否設置了 createRectTween 參數 ;
一、圓形方形組件
圓形方形變化的組件 : 該組件可以根據不同的參數實現圓形到方形的變化 , 或方形到圓形的變化 ;
/// Hero 組件 , 徑向動畫擴展 /// 該組件主要用于裁剪組件用的 class OvalRectWidget extends StatelessWidget {/// 這里的裁剪大小 clipRectSize 最大半徑 / 2 的開方值 再乘以 2const 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)/// 并且該方形組件居中顯示/// 那么該方形組件的四個頂點正好處于圓形組件的裁剪半徑位置/// 也就是方形組件完整顯示 , 沒有裁剪到@overrideWidget build(BuildContext context) {/// 布局裁剪組件 , 可以將布局裁剪成圓形return ClipOval(/// 可用于約束布局大小的組件/// 這里的居中顯示是關鍵 , 如果不居中顯示 , 最終還是圓形child: Center(child: SizedBox(width: clipRectSize,height: clipRectSize,/// 用于裁剪圓角矩形的組件child: ClipRect(child: child,),),),);} }組件形狀顯示分析 :
① 方形裁剪組件 : ClipOval 組件區域是 紅色 矩形所在位置 , 其裁剪區域是藍色組件位置 , 如果正好有個方形的組件 ClipRect 處于下面橙色區域內 , 那么該方形組件正好躲過了被外圍紅色區域 ClipOval 裁剪的操作 ; 顯示的仍然是方形的組件 ;
② 圓形裁剪組件 : 如果 ClipOval 圓形裁剪組件 ( 紅色 ) 與 ClipRect 方形的裁剪組件 ( 橙色 ) 位置重疊 , 那么該方形的裁剪組件肯定就被裁剪成圓形的了 ;
上面兩個組件就是 Hero 徑向動畫的主要作用組件 , 該動畫執行前 , 組件是圓形的 , 執行后組件是方形的 , 這就是改變了外層的 ClipOval 組件的大小 , 導致形狀改變 ;
二、創建頁面 1 的組件 ( Hero 組件 1 )
頁面 1 的 Hero 組件顯示的圓形的 , 跳轉到頁面 2 后 , 相同 tag 的 Hero 組件顯示方形 ;
控制 OvalRectWidget 是圓形還是方形 , 主要是控制 OvalRectWidget 組件的寬高 , 這里設置的寬高設置 , 相當于上面的 " ② 圓形裁剪組件 " 情況 , 整個組件被裁剪成圓形的組件 ;
創建頁面 1 的組件 :
/// 創建在界面 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) {// 創建一個 RoutePageBuilderreturn AnimatedBuilder(animation: animation,builder: (context, child) {/// 設置透明度組件return Opacity(/// 當前的透明度值 , 取值 0.0 ~ 1.0opacity: opacityCurve.transform(animation.value),// 主要顯示的使用透明度控制的組件// 頁面 2 組件child: _buildSecondPageWidget(context, imageName, description),);});}));},),),),);}三、創建頁面 2 的組件 ( Hero 組件 2 )
頁面 1 的 Hero 組件顯示的圓形的 , 跳轉到頁面 2 后 , 相同 tag 的 Hero 組件顯示方形 ;
控制 OvalRectWidget 是圓形還是方形 , 主要是控制 OvalRectWidget 組件的寬高 , 這里設置的寬高相當于上面的 " ① 方形裁剪組件 " 設置 , 整個組件沒有被裁剪到 , 顯示的是方形組件 ;
創建頁面 2 的組件 :
/// 創建頁面 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 動畫標簽 IDtag: 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,),],),),),);}四、完整代碼示例
完整代碼示例 :
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;@overrideWidget build(BuildContext context) {return Material(/// 獲取主題顏色 , 并將透明度設置為 0.25color: 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 的開方值 再乘以 2const 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)/// 并且該方形組件居中顯示/// 那么該方形組件的四個頂點正好處于圓形組件的裁剪半徑位置/// 也就是方形組件完整顯示 , 沒有裁剪到@overrideWidget 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 動畫標簽 IDtag: 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) {// 創建一個 RoutePageBuilderreturn AnimatedBuilder(animation: animation,builder: (context, child) {/// 設置透明度組件return Opacity(/// 當前的透明度值 , 取值 0.0 ~ 1.0opacity: opacityCurve.transform(animation.value),// 主要顯示的使用透明度控制的組件// 頁面 2 組件child: _buildSecondPageWidget(context, imageName, description),);});}));},),),),);}@overrideWidget 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://img-blog.csdnimg.cn/20210330094257242.png", "蜂王"),_buildFirstPagWidget(context,"https://img-blog.csdnimg.cn/20210330093526559.png", "蜜蜂"),_buildFirstPagWidget(context,"https://img-blog.csdnimg.cn/2021033009353553.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 ( 本篇博客的源碼快照 , 可以找到本博客的源碼 )
總結
以上是生活随笔為你收集整理的【Flutter】Hero 动画 ( Hero 实现径向动画 | Hero 组件 createRectTween 设置 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Flutter】手机应用类型 ( An
- 下一篇: 【Flutter】Flutter 调试