简体   繁体   English

Flutter 使用自定义画家之间的英雄过渡

[英]Flutter use Hero transition between Custom Painter

Want I want想要我想要

Hello, I want to realize a function in my application which is based on brainstorming applications.您好,我想在我的基于头脑风暴应用程序的应用程序中实现一个 function。

在此处输入图像描述


What I do我所做的

Here is my application这是我的申请

在此处输入图像描述

I have a wheel which is the first page, when I click on one of the "balls" it opens and shows me the second page.我有一个轮子,它是第一页,当我单击其中一个“球”时,它会打开并显示第二页。


My problem我的问题

I don't see how to animate the transition like on the example application.我看不到如何像示例应用程序那样为过渡设置动画。 I have to use the "Hero" transitions but I don't see how to use that in a custom painter?我必须使用“英雄”过渡,但我不知道如何在自定义画家中使用它?

I used 2 pages to do my ball opening because I want to be able to use my android back button to go back.我用了 2 页来做我的球开场,因为我希望能够使用我的 android 后退按钮到 go 后退。


The code代码

Here my Views :这是我的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 :这是我的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 : 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 : 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,
  };
}

Any guidance on the best way to accomplish this would be appreciated.任何关于实现这一目标的最佳方法的指导将不胜感激。

This is sample static code - using AnimatedPositioned and AnimatedContainer, and i write code for all component in a single file in a single class, with help of this you can create dynamically, this is just an idea.这是示例 static 代码 - 使用 AnimatedPositioned 和 AnimatedContainer,我在单个文件 class 中为单个文件中的所有组件编写代码,借助它您可以动态创建,这只是一个想法。

在此处输入图像描述

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM