Want I want
Hello, I want to realize a function in my application which is based on brainstorming applications.
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.
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,
};
}
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.
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),
),
),
),
],
),
),
),
);
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.