Flutter 在flatter中绘制一个圆形菜单
我正试图画一个圆形菜单,如下图所示,但是我对颤振还不熟悉,我不知道从何处开始 我将只有8个和平,而不是像图中所示的12个,它们都是链接,每个链接都指向应用程序的不同部分 在中间灰色区域,我们将有短文本Flutter 在flatter中绘制一个圆形菜单,flutter,flutter-layout,Flutter,Flutter Layout,我正试图画一个圆形菜单,如下图所示,但是我对颤振还不熟悉,我不知道从何处开始 我将只有8个和平,而不是像图中所示的12个,它们都是链接,每个链接都指向应用程序的不同部分 在中间灰色区域,我们将有短文本 我希望它能给我们一些开始。 步骤: import 'dart:math' as math; import 'package:flutter/material.dart'; const double degrees2Radians = math.pi / 180.0; void main()
我希望它能给我们一些开始。 步骤:
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({
Key key,
this.angle,
this.buttonData,
}) : super(key: key);
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;
}
ShapeDecoration
reClipPath
,我选择了ClipPath
,但是使用ShapeDecoration
可以添加阴影Matrix4
进行路径
或变换。旋转
进行旋转。我选择了“Transform.rotate”两次,速度更快,但使用更干净的方法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({
Key key,
this.angle,
this.buttonData,
}) : super(key: key);
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;
}
导入'dart:math'作为数学;
进口“包装:颤振/材料.省道”;
常数双度2半径=math.pi/180.0;
void main()=>runApp(MyApp());
类MyApp扩展了无状态小部件{
@凌驾
小部件构建(构建上下文){
返回材料PP(
标题:“颤振演示”,
主题:主题数据(
主样本:颜色。蓝色,
),
家:脚手架(
背景颜色:Colors.amber,
正文:安全区(
子:MyHomePage(),
),
),
);
}
}
类MyHomePage扩展了无状态小部件{
最后项目=[
按钮数据(标题:“一”,onTap:()=>打印('1'),
按钮数据(标题:“两个”,onTap:()=>打印('2'),
按钮数据(标题:“三”,onTap:()=>打印('3'),
按钮数据(标题:“四”,onTap:()=>打印('4'),
按钮数据(标题:“五”,onTap:()=>打印('5'),
按钮数据(标题:“六”,onTap:()=>打印('6'),
按钮数据(标题:“七”,onTap:()=>打印('7'),
按钮数据(标题:“八”,onTap:()=>打印('8'),
按钮数据(onTap:()=>打印('center'),
];
@凌驾
小部件构建(构建上下文){
返回容器(
身高:300,
宽度:300,
子:堆栈(
儿童:项目
.asMap()
.map((索引,按钮数据){
如果(指数<8){
var度=360/8*指数;
var弧度=度*度2半径;
返回映射条目(
指数
对齐(
对齐:对齐(
数学。罪恶(弧度),
数学cos(弧度),
),
子对象:Transform.rotate(
角度:-弧度,
子项:菜单金属(角度:-弧度,按钮数据:按钮数据),
),
),
);
}
返回映射条目(
指数
_中心按钮(按钮数据),
);
})
价值观
.toList(),
),
);
}
小部件_中心按钮(按钮数据按钮数据){
返回中心(
孩子:ClipRRect(
边界半径:边界半径。圆形(25),
儿童:手势检测器(
onTap:buttonda.onTap,
子:容器(
宽度:50,
身高:50,
颜色:颜色。黑色38,
),
),
),
);
}
}
班顿达达{
最后的字符串标题;
最终的void函数()onTap;
ButtonData({this.title,this.onTap});
}
类MenuPetal扩展了无状态小部件{
康斯特梅努佩塔酒店({
关键点,
这个角度,
这个,巴顿达,
}):super(key:key);
最终双角度;
最终按钮数据按钮数据;
最终双因子=0.38;
@凌驾
小部件构建(构建上下文){
返回分馏液箱(
高度系数:系数,
宽度系数:系数,
儿童:手势检测器(
onTap:buttonda.onTap,
孩子:克利帕斯(
clipper:MyCustomClipper(),
子:容器(
对齐:对齐.center,
装饰:盒子装饰(
图像:装饰图像(
fit:BoxFit.fill,
图片:
网络映像('https://source.unsplash.com/featured/?white'),
),
),
孩子:填充(
填充:仅限边缘设置(顶部:60),
子对象:Transform.rotate(
角度:-角度,
子项:文本(buttonData.title),
),
),
),
),
),
);
}
}
类MyCustomClipper扩展了CustomClipper{
@凌驾
路径getClip(大小){
var x=尺寸宽度/100*0.802;
变量y=尺寸高度/100;
var path=path()
…移动到(39.4*x,6.1*y)
…立方(43.2*x,-1.8*y,57.1*x,-1.8*y,60.9*x,6.1*y)
..直线至(99.1*x,84.1*y)
…立方体(102.1*x,90.2*y,99.1*x,93.9*y,92.0*x,95.6*y)
…立方体(67.4*x,101.7*y,36.9*x,101.7*y,9.2*x,95.6*y)
…立方(1.2*x,93.8*y,-1.3*x,88.7*y,1.2*x,84.1*y)
..线规(39.4*x,6.1*y);
返回路径移位(偏移量(12,0));
}
@凌驾
bool shouldReclip(CustomClipper oldClipper)=>true;
}