Flutter 在flatter中绘制一个圆形菜单

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()

我正试图画一个圆形菜单,如下图所示,但是我对颤振还不熟悉,我不知道从何处开始

我将只有8个和平,而不是像图中所示的12个,它们都是链接,每个链接都指向应用程序的不同部分

在中间灰色区域,我们将有短文本


我希望它能给我们一些开始。

步骤:

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;
}

  • 通过以下方式绘制svg
  • 使用软件包从svg路径创建颤振路径
  • 创建
    ShapeDecoration
    re
    ClipPath
    ,我选择了
    ClipPath
    ,但是使用
    ShapeDecoration
    可以添加阴影
  • 使用
    Matrix4
    进行
    路径
    变换。旋转
    进行旋转。我选择了“Transform.rotate”两次,速度更快,但使用更干净的方法
  • 使用角度的正弦和余弦计算菜单中每个花瓣的位置
  • 把一切都调一调
  • 快乐编码!享受吧

    p、 如果我们不使用特定数量的零件来解决问题,可能会更好

    示例代码:

    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;
    }