Reputation: 1977
I am trying to draw a circle menu as seen in the attached image below, however I am new to Flutter and I have no idea where to start with something like this.
I will only have 8 peaces instead of 12 as seen in the image, and they will all be links, each link going to a different section of the app.
In the centre grey area, we will have short text
Upvotes: 3
Views: 2233
Reputation: 16205
I hope it will give something to start.
Steps:
Path
from svg path
ShapeDecoration
or ClipPath
, I've choose ClipPath
, but with ShapeDecoration
you can add the shadows.Matrix4
for Path
or Transform.rotate
to rotate. I've choose `Transform.rotate twice it's was faster but using Path.transform is much cleaner approach.Happy coding! Enjoy!
p.s. It could be better if we do solution without specific number of pieces.
the sample code:
import 'dart:math' as math;
import 'package:flutter/material.dart';
const double degrees2Radians = math.pi / 180.0;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
backgroundColor: Colors.amber,
body: SafeArea(
child: MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatelessWidget {
final items = [
ButtonData(title: 'one', onTap: () => print('1')),
ButtonData(title: 'two', onTap: () => print('2')),
ButtonData(title: 'three', onTap: () => print('3')),
ButtonData(title: 'four', onTap: () => print('4')),
ButtonData(title: 'five', onTap: () => print('5')),
ButtonData(title: 'six', onTap: () => print('6')),
ButtonData(title: 'seven', onTap: () => print('7')),
ButtonData(title: 'eight', onTap: () => print('8')),
ButtonData(onTap: () => print('center')),
];
@override
Widget build(BuildContext context) {
return Container(
height: 300,
width: 300,
child: Stack(
children: items
.asMap()
.map((index, buttonData) {
if (index < 8) {
var degree = 360 / 8 * index;
var radian = degree * degrees2Radians;
return MapEntry(
index,
Align(
alignment: Alignment(
math.sin(radian),
math.cos(radian),
),
child: Transform.rotate(
angle: -radian,
child: MenuPetal(angle: -radian, buttonData: buttonData),
),
),
);
}
return MapEntry(
index,
_centerButton(buttonData),
);
})
.values
.toList(),
),
);
}
Widget _centerButton(ButtonData buttonData) {
return Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(25),
child: GestureDetector(
onTap: buttonData.onTap,
child: Container(
width: 50,
height: 50,
color: Colors.black38,
),
),
),
);
}
}
class ButtonData {
final String? title;
final void Function()? onTap;
ButtonData({this.title, this.onTap});
}
class MenuPetal extends StatelessWidget {
const MenuPetal({
super.key,
required this.angle,
required this.buttonData,
});
final double angle;
final ButtonData buttonData;
final double factor = 0.38;
@override
Widget build(BuildContext context) {
return FractionallySizedBox(
heightFactor: factor,
widthFactor: factor,
child: GestureDetector(
onTap: buttonData.onTap,
child: ClipPath(
clipper: MyCustomClipper(),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image:
NetworkImage('https://source.unsplash.com/featured/?white'),
),
),
child: Padding(
padding: EdgeInsets.only(top: 60),
child: Transform.rotate(
angle: -angle,
child: Text(buttonData.title ?? ""),
),
),
),
),
),
);
}
}
class MyCustomClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
var x = size.width / 100 * 0.802;
var y = size.height / 100;
var path = Path()
..moveTo(39.4 * x, 6.1 * y)
..cubicTo(43.2 * x, -1.8 * y, 57.1 * x, -1.8 * y, 60.9 * x, 6.1 * y)
..lineTo(99.1 * x, 84.1 * y)
..cubicTo(102.1 * x, 90.2 * y, 99.1 * x, 93.9 * y, 92.0 * x, 95.6 * y)
..cubicTo(67.4 * x, 101.7 * y, 36.9 * x, 101.7 * y, 9.2 * x, 95.6 * y)
..cubicTo(1.2 * x, 93.8 * y, -1.3 * x, 88.7 * y, 1.2 * x, 84.1 * y)
..lineTo(39.4 * x, 6.1 * y);
return path.shift(Offset(12, 0));
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
Upvotes: 7