Flutter 使用 Hero 在 Custom Painter 之间转换

2024-02-25

想要我想要

您好,我想在我的应用程序中实现一个基于头脑风暴应用程序的功能。


我做什么

这是我的申请

我有一个轮子,它是第一页,当我单击其中一个“球”时,它会打开并显示第二页。


我的问题

我不知道如何像示例应用程序那样为过渡设置动画。我必须使用“英雄”过渡,但我不知道如何在自定义画家中使用它?

我用了 2 页来打开球,因为我希望能够使用 Android 后退按钮返回。


The code

Here my Views :

import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:touchable/touchable.dart';

class FirstView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    var controller = Provider.of<CompteurProvider>(context);

    return SafeArea(
      child: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraints){
            return Column(
              children: [
                Hero(
                  tag: "Hero",
                  child: ChangeNotifierProvider<CompteurProvider>(
                    create: (context) => CompteurProvider(),
                    child: CanvasTouchDetector(
                        builder: (context) {
                          return CustomPaint(
                            size: Size(constraints.maxWidth, constraints.maxHeight),
                            painter: AreasPainter(
                              context,
                              controller.areas,
                            ),
                          );
                        }
                    ),
                  ),
                ),
              ],
            );
          },
        )
      ),
    );
  }
}

class SecondView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    var controller = Provider.of<CompteurProvider>(context);

    return SafeArea(
      child: Scaffold(
          body: LayoutBuilder(
            builder: (context, constraints){
              return Column(
                children: [
                  Hero(
                    tag: "Hero",
                    child: ChangeNotifierProvider<CompteurProvider>(
                      create: (context) => CompteurProvider(),
                      child: CanvasTouchDetector(
                          builder: (context) {
                            return CustomPaint(
                              size: Size(constraints.maxWidth, constraints.maxHeight),
                              painter: SubAreasPainter(
                                context,
                                controller.area!,
                              ),
                            );
                          }
                      ),
                    ),
                   ),
                ],
              );
            },
          )
      ),
    );
  }
}

Here my Painter:

import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:touchable/touchable.dart';
import 'package:provider/provider.dart';

class AreasPainter extends CustomPainter
{
  final List<AreaEntity> areas;
  final BuildContext context;
  final CompteurProvider controller;
  final int dotsPerRing;

  AreasPainter(this.context, this.areas)
      : dotsPerRing = areas.length,
        controller = Provider.of<CompteurProvider>(context);

  final double dotRadius = 40;

  @override
  void paint(Canvas canvas, Size size) {
    TouchyCanvas touchyCanvas = TouchyCanvas(context,canvas);

    // General variable
    final Offset centerOffset = Offset(size.width / 2, size.height / 2);
    final double centerCircleRadius = size.height / 2 - dotRadius - 10;
    final double betweenAngle = 2 * pi / dotsPerRing;

    // Center circle  ----------------------------------------------------------
    drawCenterCircle(
      canvas: canvas,
      centerOffset: controller.circlePosition ?? centerOffset,
      radius: centerCircleRadius,
    );
    // Big Circle   ------------------------------------------------------------
    drawBigCircle(
      canvas: canvas,
      centerOffset: centerOffset,
      radius: centerCircleRadius,
    );

    // Balls around   ----------------------------------------------------------
    areas.forEach((area) {
      drawBall(
          canvas: canvas,
          touchyCanvas : touchyCanvas,
          centerOffset : centerOffset,
          offsetPositionOnCircle : getPositionOnCircle(betweenAngle: betweenAngle, id: area.id, radius: centerCircleRadius),
          subValues: area.subAreas,
          name: area.text,
          dotRadius: dotRadius,
          onTapAction: (){
            print(area.text);
            Navigator.pushNamed(context, RouterName.kTest2, arguments: area);
          }
      );
    });
  }

  void drawCenterCircle({
    required Canvas canvas,
    required Offset centerOffset,
    required double radius
  }){
    // -------------------------------------------------------------------------
    Paint outCirclePaint = Paint()
      ..color = AppColors.kcolor_bleu
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2;
    canvas.drawCircle(centerOffset, 20, outCirclePaint);
    // -------------------------------------------------------------------------
    Paint inCirclePaint = Paint()
      ..color = AppColors.kcolor_bleu
      ..strokeCap = StrokeCap.round;
    canvas.drawCircle(centerOffset, 5, inCirclePaint);
    // -------------------------------------------------------------------------
    final text = measureText(text: "Areas", style: TextStyle(color: AppColors.kcolor_bleu, fontSize: 10));
    text.paint(canvas, centerOffset - Offset(text.width / 2, -25));
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

class SubAreasPainter extends CustomPainter
{
  final AreaEntity area;
  final BuildContext context;
  final CompteurProvider controller;
  final int dotsPerRing;

  SubAreasPainter(this.context, this.area)
      : dotsPerRing = area.subAreas.length,
        controller = Provider.of<CompteurProvider>(context);

  final double dotRadius = 40;

  @override
  void paint(Canvas canvas, Size size) {
    TouchyCanvas touchyCanvas = TouchyCanvas(context,canvas);

    // General variable
    final Offset centerOffset = Offset(size.width / 2, size.height / 2);
    final double centerCircleRadius = size.height / 2 - dotRadius - 10;
    final double betweenAngle = 2 * pi / dotsPerRing;

    // Center circle  ----------------------------------------------------------
    drawBall(
        canvas: canvas,
        touchyCanvas : touchyCanvas,
        centerOffset : centerOffset,
        subValues: area.subAreas,
        name: area.text,
        dotRadius: dotRadius,
        onTapAction: (){}
    );

    // Big Circle   ------------------------------------------------------------
    drawBigCircle(
      canvas: canvas,
      centerOffset: centerOffset,
      radius: centerCircleRadius,
    );

    // Balls around   ----------------------------------------------------------
    area.subAreas.forEach((subArea) {
      drawBall(
          canvas: canvas,
          touchyCanvas : touchyCanvas,
          centerOffset : centerOffset,
          offsetPositionOnCircle : getPositionOnCircle(betweenAngle: betweenAngle, id: subArea.id, radius: centerCircleRadius),
          subValues: area.subAreas,
          name: subArea.text,
          dotRadius: dotRadius,
          onTapAction: (){
            Navigator.pop(context);
          }
      );
    });
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}


Offset getPositionOnCircle({
  required double betweenAngle,
  required int id,
  required double radius
}){
  double angleFromStart = betweenAngle * id;
  return Offset(radius * cos(angleFromStart), radius * sin(angleFromStart));
}

TextPainter measureText({
  required String text,
  TextStyle? style
})
{
  final textSpan = TextSpan(text: text, style: style != null ? style : TextStyle(color: Colors.white));
  final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr);
  textPainter.layout(minWidth: 0, maxWidth: double.maxFinite);
  return textPainter;
}

 drawBall({
  required Canvas canvas,
  required TouchyCanvas touchyCanvas,
  required Offset centerOffset,
  Offset offsetPositionOnCircle = const Offset(0,0),
  required List subValues,
  required String name,
  required dotRadius,
  Function? onTapAction
}){
  // Dot background --------------------------------------------------------
  Paint dotBackgroundPaint = Paint()
    ..color = AppColors.kBg_dark;
  touchyCanvas.drawCircle(centerOffset + offsetPositionOnCircle, dotRadius + 4, dotBackgroundPaint);

  // Dot -------------------------------------------------------------------
  Paint dotPaint = Paint()
    ..color = AppColors.kBg_light
    ..strokeCap = StrokeCap.round
    ..style = PaintingStyle.stroke
    ..strokeWidth = 2;

  touchyCanvas.drawCircle(centerOffset + offsetPositionOnCircle, dotRadius, dotPaint);

  // Text ------------------------------------------------------------------
  final textSize = measureText(
      text: name,
      style: TextStyle(
        color: AppColors.kFont_grey,
        fontSize: 8.0,
        fontWeight: FontWeight.bold,
      )
  );
  final Offset offsetCenterText = Offset(- textSize.width / 2.0 , - textSize.height / 2.0);
  textSize.paint(canvas, centerOffset + offsetPositionOnCircle + offsetCenterText);

  // Touch area ----------------------------------------------------------------
  Paint toucheAreaPaint = Paint()
    ..color = Colors.transparent;
  touchyCanvas.drawCircle(centerOffset + offsetPositionOnCircle, dotRadius, toucheAreaPaint,
      onTapDown: (t) {
        onTapAction!();
      }
  );
}

void drawBigCircle({
  required Canvas canvas,
  required Offset centerOffset,
  required double radius
}){
  Paint defaultCirclePaint = Paint()
    ..color = AppColors.kBg_normal.withOpacity(1)
    ..strokeCap = StrokeCap.round
    ..style = PaintingStyle.stroke
    ..strokeWidth = 3;
  canvas.drawCircle(centerOffset, radius, defaultCirclePaint);
}

The provider:

class CompteurProvider with ChangeNotifier {
  // Variables
  // ---------------------------------------------------------------------------
  Future? dataToLoad;
  late List<AreaEntity> areas = [];
  late double centerRingSize;
  late AreaEntity? area;

  // Constructor
  // ---------------------------------------------------------------------------
  CompteurProvider({
    this.area
  }){
    _initialise();
  }

  // Initialisation
  // ---------------------------------------------------------------------------
  Future _initialise() async
  {
    dataToLoad = await loadingData();
    notifyListeners();
  }

  Future loadingData() async
  {
    centerRingSize = 20;
    areas.add(AreaEntity(
        id: 0,
        text: "AREA 1",
        subAreas : [
          SubAreaEntity(id: 0, text: "E1"),
          SubAreaEntity(id: 1, text: "E2")
        ]
    ));
    areas.add(AreaEntity(
        id: 1,
        text: "AREA 2",
        subAreas : []
    ));
    areas.add(AreaEntity(
        id: 2,
        text: "AREA 3",
        subAreas : []
    ));
    areas.add(AreaEntity(
        id: 3,
        text: "AREA 4",
        subAreas : []
    ));
    areas.add(AreaEntity(
        id: 4,
        text: "AREA 5",
        subAreas : []
    ));

    if(area != null){
      area = area;
    }

    notifyListeners();
  }
}

The AreaEntity:

import 'package:equatable/equatable.dart';

class AreaEntity extends Equatable{
  int id;
  String text;
  List<SubAreaEntity> subAreas;

  AreaEntity({
    required this.id,
    required this.text,
    required this.subAreas,
  });

  @override
  List<Object> get props{
    return [
      id,
      text,
      subAreas
    ];
  }

  Map<String, dynamic> toJson() => {
    "id" : id,
    "text" : text,
    "subAreas" : subAreas,
  };
}

class SubAreaEntity extends Equatable{
  int id;
  String text;

  SubAreaEntity({
    required this.id,
    required this.text,
  });

  @override
  List<Object> get props{
    return [
      id,
      text,
    ];
  }

  Map<String, dynamic> toJson() => {
    "id" : id,
    "text" : text,
  };
}

任何关于实现这一目标的最佳方法的指导将不胜感激。


这是示例静态代码 - 使用 AnimatedPositioned 和 AnimatedContainer,我在单个类的单个文件中编写所有组件的代码,借助此您可以动态创建,这只是一个想法。

import 'package:flutter/material.dart';

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

  @override
  State<GlobeExample> createState() => _GlobeExampleState();
}

class _GlobeExampleState extends State<GlobeExample> {
  bool isExpand = false;
  double sizeValue = 75.0, lineSize = 0.0;
  int lineDuration = 1000;
  @override
  Widget build(BuildContext context) {
    final Size size = MediaQuery.of(context).size;
    return Scaffold(
      backgroundColor: Colors.white,
      body: SizedBox(
        height: size.height,
        width: size.width,
        child: GestureDetector(
          onTap: () => setState(() {
            isExpand = !isExpand;
            isExpand == true ? sizeValue = 100.0 : sizeValue = 75.0;
            isExpand == true ? lineDuration = 800 : lineDuration = 1000;
            isExpand == true ? lineSize = 400.0 : lineSize = 0.0;
          }),
          child: Stack(
            alignment: Alignment.center,
            children: [
              AnimatedContainer(
                duration: const Duration(seconds: 1),
                height: isExpand == true ? 400.0 : 250.0,
                width: isExpand == true ? 400.0 : 250.0,
                decoration: BoxDecoration(
                  border: Border.all(width: 2.5, color: Colors.blueGrey),
                  borderRadius: BorderRadius.circular(250.0),
                ),
              ),
              Container(
                height: 100.0,
                width: 100.0,
                decoration: BoxDecoration(
                  color: Colors.blueGrey,
                  borderRadius: BorderRadius.circular(100.0),
                ),
              ),
              AnimatedContainer(
                duration: Duration(milliseconds: lineDuration),
                height: lineSize,
                width: 2.0,
                decoration: BoxDecoration(
                  border: Border.all(width: 2.5, color: Colors.blueGrey),
                ),
              ),
              AnimatedContainer(
                duration: Duration(milliseconds: lineDuration),
                height: 2.0,
                width: lineSize,
                decoration: BoxDecoration(
                  border: Border.all(width: 2.5, color: Colors.blueGrey),
                ),
              ),
              Transform.rotate(
                angle: 0.77,
                child: AnimatedContainer(
                  duration: Duration(milliseconds: lineDuration),
                  height: lineSize,
                  width: 2.0,
                  decoration: BoxDecoration(
                    border: Border.all(width: 2.5, color: Colors.blueGrey),
                  ),
                ),
              ),
              Transform.rotate(
                angle: -0.77,
                child: AnimatedContainer(
                  duration: Duration(milliseconds: lineDuration),
                  height: lineSize,
                  width: 2.0,
                  decoration: BoxDecoration(
                    border: Border.all(width: 2.5, color: Colors.blueGrey),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                top: isExpand == true ? 200.0 : 290.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                bottom: isExpand == true ? 200.0 : 290.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                right: isExpand == true ? -38.0 : 50.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                left: isExpand == true ? -38.0 : 50.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                bottom: isExpand == true ? 256.0 : 324.0,
                right: isExpand == true ? 20.0 : 80.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                bottom: isExpand == true ? 256.0 : 324.0,
                left: isExpand == true ? 20.0 : 80.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                top: isExpand == true ? 256.0 : 324.0,
                right: isExpand == true ? 20.0 : 80.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
              AnimatedPositioned(
                duration: const Duration(seconds: 1),
                top: isExpand == true ? 256.0 : 324.0,
                left: isExpand == true ? 20.0 : 80.0,
                child: AnimatedContainer(
                  duration: const Duration(seconds: 1),
                  height: sizeValue,
                  width: sizeValue,
                  decoration: BoxDecoration(
                    color: Colors.blueGrey,
                    borderRadius: BorderRadius.circular(sizeValue),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Flutter 使用 Hero 在 Custom Painter 之间转换 的相关文章

随机推荐

  • SQL Server:类似中使用的索引列?

    对仅在 LIKE 操作中使用的 varchar 列建立索引是个好主意吗 根据我从查询分析中读到的内容 我从以下查询中得到 SELECT FROM ClientUsers WHERE Email LIKE niels bosmainter 在
  • 为什么 Angular.js 使用 ng-change 如此频繁地调用我的函数以及如何让它每次更改只调用一次?

    在学习了许多教程之后 我正在构建我的第一个合适的 angular js 应用程序 我遇到了一个问题ngChange指示 每次用户更改下拉列表的值时 我都尝试使用它来调用函数 我发现它在页面加载时多次调用该函数 并且每次选择该选项时也会多次调
  • 如何像 Evernote Clipper 那样检测主文章标签

    When I tried with Evernote clipper extension https chrome google com webstore detail evernote web clipper pioclpoplcdbae
  • BitmapFactory.decodeResource 和 BitmapFactory.decodeStream 之间的差异

    我有一个 if else 场景 其中 if path 使用以下代码 BitmapFactory Options options new BitmapFactory Options options inScaled false options
  • 编译时错误:“main”的多重定义

    我收到以下错误 main 的多重定义 我创建了一个新项目 里面有两个c 文件 File 1 include
  • SQL 查询帮助 - 每个不同列值 10 条记录

    我有一个包含汽车列表的汽车表 表结构看起来像 cars id title make year 我想要一个返回每个品牌 10 辆汽车的查询 相当于以下伪代码 car makes select distinct make from cars f
  • 在播放时预加载 html5 音频

    对于 HTML5 音频 假设您有一个要播放的两首歌曲的列表 目前我已将其设置为当当前歌曲停止播放时 它会加载新歌曲并播放它 我希望它能够在当前歌曲结束时加载下一首歌曲 也许在当前歌曲结束前 20 秒 我尝试在播放歌曲时更改音频对象的 src
  • 确定客户端绑定的 TCP 端口号

    我创建了一个 TCP 套接字 而不关心要绑定到 socket sin port 0 的端口号 但是稍后如果我想打印客户端的端口号我该怎么做 客户端 C 应用程序 在 Linux 上 创建许多连接到服务器的客户端 为了调试问题 我捕获了 et
  • 如何在 Groovy 中将 String 转换为 GString 并替换占位符?

    我想从数据库读取字符串并通过将其转换为 GString 来替换占位符 我可以用 Eval 来做这个吗 还有其他想法吗 String stringFromDatabase Hello name String name world assert
  • 使用负数缩放值范围

    如果一组值包含负数 如何缩放它们以适应新的范围 例如 我有一组数字 10 9 1 4 10 它们必须缩放到范围 0 1 这样 10 映射到 0 10 映射到 1 任意数字 x 的常规方法是 x from min to max to min
  • 如何使用 Init() 方法在 LibGDX 中重新启动屏幕?

    我在 LibGDX 中创建了一个具有多个屏幕的简单游戏 我想在触摸重启按钮后重新启动某个屏幕 但我不知道该怎么做 我对此做了一些研究 所有答案都导致不在 show 中加载我的资产 而是在我不太熟悉的 init 方法中加载 我想知道如何使用这
  • 如何在Java Springboot中使用jdbcTemplate将整数数组插入postgresql表?

    我在将整数数组插入 Postgresql 表时遇到问题 该怎么办 String sql INSERT INTO draw result id ball numbers balls with mega ball draw dates mega
  • 在一个查询中从三个表获取数据

    我试图同时从三个表中检索数据 这些表格看起来像 类别 id category messageid messages id title message comments id messageid message 我想要得到的是 1 条消息 因
  • VBA 和 IE8 - 输入值并搜索

    我在工作中有一个基于 Intranet 的网站 我想输入 SKU 并使用 VBA 和 IE8 将出现的数据抓取到 Excel 工作表中 目前正在等待访问许可证批准和批准才能访问我们的 IBM as400 服务器 IE8不支持getEleme
  • 如何在heroku中显示来自node.js的所有console.log?

    我已将 Node js 应用程序部署到 Node js 但无法从我的应用程序中看到完整的 console log 语句 我在用 heroku logs 显示了一些日志记录 但看起来不是完整的日志 是否有一个 node js 包可以从已部署的
  • 未添加 WordPress 过滤器

    我有一个使用的插件apply filters像这样 additional fields apply filters attachment meta add fields additional fields 在我的主题中functions p
  • 从 Clojure 中的数据结构实现细节中抽象出来

    我正在 Clojure 中开发一个具有多个子结构的复杂数据结构 我知道我会想要随着时间的推移扩展这个结构 并且有时可能想要更改内部结构而不破坏数据结构的不同用户 例如我可能想将向量更改为哈希图 添加某种索引出于性能原因的结构 或合并 Jav
  • Bootstrap Carousel - 如何在幻灯片之间缓慢淡入淡出

    我正在使用最新的 Bootstrap Carousel 需要在幻灯片之间缓慢淡出 大约 5 秒 我看过很多例子 并尝试实现这个one https stackoverflow com questions 27861435 bootstrap
  • 比较和对比 REST 和 SOAP Web 服务? [复制]

    这个问题在这里已经有答案了 我目前发现类似的情况都是使用互联网协议 HTTP 在消费者和提供商之间交换数据 区别在于 SOAP是一种基于XML的消息协议 而REST是一种架构风格 SOAP 使用 WSDL 进行消费者和提供者之间的通信 而
  • Flutter 使用 Hero 在 Custom Painter 之间转换

    想要我想要 您好 我想在我的应用程序中实现一个基于头脑风暴应用程序的功能 我做什么 这是我的申请 我有一个轮子 它是第一页 当我单击其中一个 球 时 它会打开并显示第二页 我的问题 我不知道如何像示例应用程序那样为过渡设置动画 我必须使用