flutter 圆形控制器

91 阅读2分钟

image.png

import 'dart:math';

import 'package:flutter/material.dart';

///云台控制器
class CircularController extends StatelessWidget {
  final Function()? onUpClick;
  final Function()? onDownClick;
  final Function()? onLeftClick;
  final Function()? onRightClick;

  CircularController({
    Key? key,
    @required this.onUpClick, 
    @required this.onDownClick,
    @required this.onLeftClick,
    @required this.onRightClick,
  }) : super(key: key);

  //大圆半径
  double largeRadius = 100.0;

  //小圆半径
  double smallRadius = 20.0;

  //图标到顶端的间隔,自由设置
  double interval = 40 / sqrt(2);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTapUp: (details) {
        // 获取点击位置的坐标
        final RenderBox renderBox = context.findRenderObject() as RenderBox;
        final localPosition = renderBox.globalToLocal(details.globalPosition);

        // 计算点击位置相对于中心的角度
        final double dx = localPosition.dx - largeRadius;
        final double dy = localPosition.dy - largeRadius;
        final double distanceToCenter = sqrt(dx * dx + dy * dy);
        final double angle = atan2(dy, dx);

        if (distanceToCenter <= smallRadius) {
          // 如果点击位置距离中心小于白色小圆的半径,表示点击在白色小圆内部,不处理点击事件
          return;
        }

        // 根据角度判断点击的是哪个扇形块
        if (angle >= -pi / 4 && angle < pi / 4) {
          // 点击了右侧扇形块
          // 处理点击右侧扇形块的逻辑
          onRightClick!();
        } else if (angle >= pi / 4 && angle < 3 * pi / 4) {
          // 点击了下方扇形块
          // 处理点击下方扇形块的逻辑
          onDownClick!();
        } else if (angle >= -3 * pi / 4 && angle < -pi / 4) {
          // 点击了上方扇形块
          // 处理点击上方扇形块的逻辑
          onUpClick!();
        } else {
          // 点击了左侧扇形块
          // 处理点击左侧扇形块的逻辑
          onLeftClick!();
        }
      },
      child: SizedBox(
        width: largeRadius * 2,
        height: largeRadius * 2,
        child: Stack(
          children: [
            // 自定义绘制
            CustomPaint(
              size: Size(largeRadius * 2, largeRadius * 2),
              painter: CircularControllerPainter(),
            ),
            // 白色小圆
            Center(
              child: Container(
                width: smallRadius * 2,
                height: smallRadius * 2,
                decoration: const BoxDecoration(shape: BoxShape.circle, color: Colors.white),
              ),
            ),
            Padding(
              padding: EdgeInsets.symmetric(vertical: interval),
              child: const Align(
                alignment: Alignment.topCenter,
                child: Icon(Icons.arrow_upward, color: Colors.black),
              ),
            ),

            Padding(
              padding: EdgeInsets.symmetric(vertical: interval),
              child: const Align(
                alignment: Alignment.bottomCenter,
                child: Icon(Icons.arrow_downward, color: Colors.black),
              ),
            ),
            Padding(
              padding: EdgeInsets.symmetric(horizontal: interval),
              child: const Align(
                alignment: Alignment.centerLeft,
                child: Icon(Icons.arrow_back, color: Colors.black),
              ),
            ),
            Padding(
              padding: EdgeInsets.symmetric(horizontal: interval),
              child: const Align(
                alignment: Alignment.centerRight,
                child: Icon(Icons.arrow_forward, color: Colors.black),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class CircularControllerIconsPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final double radius = size.width / 2;
    final Offset center = Offset(size.width / 2, size.height / 2);

    // 绘制上下左右图标
    final Paint iconPaint = Paint()
      ..color = Colors.black
      ..style = PaintingStyle.fill;

    const double iconSize = 20.0;

    // 上
    canvas.drawCircle(
      Offset(center.dx, center.dy - radius + (iconSize / 2)),
      iconSize / 2,
      iconPaint,
    );

    // 下
    canvas.drawCircle(
      Offset(center.dx, center.dy + radius - (iconSize / 2)),
      iconSize / 2,
      iconPaint,
    );

    // 左
    canvas.drawCircle(
      Offset(center.dx - radius + (iconSize / 2), center.dy),
      iconSize / 2,
      iconPaint,
    );

    // 右
    canvas.drawCircle(
      Offset(center.dx + radius - (iconSize / 2), center.dy),
      iconSize / 2,
      iconPaint,
    );
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}

class CircularControllerPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final Paint redPaint = Paint()
      ..color = Colors.grey
      ..style = PaintingStyle.fill; // 使用fill来填充颜色

    final Paint whitePaint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 2.0;

    final double radius = size.width / 2;
    final Offset center = Offset(size.width / 2, size.height / 2);

    // 绘制四个红色扇形块和白色X形分割线
    const double startAngle = -pi / 4;
    const double sweepAngle = pi / 2;

    for (int i = 0; i < 4; i++) {
      final Rect rect = Rect.fromCircle(
        center: center,
        radius: radius,
      );
      canvas.drawArc(rect, startAngle + (i * pi / 2), sweepAngle, true, redPaint);

      // 绘制白色X形分割线
      final double lineStartAngle = startAngle + (i * pi / 2);
      final double lineEndAngle = lineStartAngle + sweepAngle;
      final Offset lineStart1 = Offset(
        center.dx,
        center.dy,
      );
      final Offset lineEnd1 = Offset(
        center.dx + (radius * cos(lineStartAngle)),
        center.dy + (radius * sin(lineStartAngle)),
      );
      final Offset lineStart2 = Offset(
        center.dx,
        center.dy,
      );
      final Offset lineEnd2 = Offset(
        center.dx + (radius * cos(lineEndAngle)),
        center.dy + (radius * sin(lineEndAngle)),
      );

      canvas.drawLine(lineStart1, lineEnd1, whitePaint);
      canvas.drawLine(lineStart2, lineEnd2, whitePaint);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }
}