首页 > 解决方案 > 在 Flutter 中绘制圆形菜单

问题描述

我正在尝试绘制一个圆形菜单,如下图所示,但是我是 Flutter 的新手,我不知道从哪里开始这样的事情。

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

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

标签: flutterflutter-layout

解决方案


我希望它会给一些东西开始。

在此处输入图像描述

脚步:

  1. 通过类似Figma的方式绘制 svg
  2. 使用path_parsingPathsvg path
  3. Create ShapeDecorationre ClipPath,我已经选择ClipPath了,但是ShapeDecoration你可以添加阴影。
  4. 用于或旋转Matrix4。我选择了 `Transform.rotate 两次,它更快,但使用Path.transform是更清洁的方法。PathTransform.rotate
  5. 使用角度的正弦和余弦计算菜单每个花瓣的位置。
  6. 稍微调整一下。

快乐编码!享受!

ps 如果我们在没有特定数量的情况下进行解决方案可能会更好。

示例代码:

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


推荐阅读