我如何在 Flutter 中设计这样的自定义 JoystickView?

How can I design such custom JoystickView in Flutter?

我想像这样构建 JoystickView:

我最终得到了这个设计:

这是我的代码:

JoystickView(
      showArrows: true,
      innerCircleColor: Color(0xFF9510AC),
      backgroundColor: Color(0xFF2D9BF0),
      //size: 100,
      size: MediaQuery.of(context).size.width * 0.45,
),

一个非常谦虚和善良的灵魂Day Vy帮我给出了设计和代码。我真的很感激他的努力。上帝保佑他。 这是代码和屏幕记录。

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

enum JoystickDirection {
  FORWARD,
  REVERSE,
  LEFT,
  RIGTH,
  STABLE,
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: MainHandle(),
    );
  }
}

class MainHandle extends StatefulWidget {
  @override
  _MainHandleState createState() => _MainHandleState();
}

class _MainHandleState extends State<MainHandle> {
  JoystickDirection joystickDirection = JoystickDirection.STABLE;
  GlobalKey joystickBubbleKey = GlobalKey();
  GlobalKey joyPadKey = GlobalKey();
  Alignment bubblealignment = Alignment(0.1, 0.1);
  double width = 70.0, height = 70.0;
  double _x = 0;
  double _y = 0;
  Color color = Colors.white24;
  var joyPadBox;

  var joyPadpos;

  late RenderBox bubbleBox;
  _onDragStarted(DragUpdateDetails details) {
    _dragwithinConstraints(details);
  }

  _dragwithinConstraints(DragUpdateDetails details) {
    // first check if its within pad rectangular box
    var offset = details.globalPosition;

    if (offset.dx >= joyPadpos.dx + (bubbleBox.size.width / 2) &&
        offset.dy >= joyPadpos.dy + (bubbleBox.size.height / 2) &&
        offset.dy <=
            joyPadpos.dy +
                (joyPadBox.size.height) -
                (bubbleBox.size.height / 2) &&
        offset.dx <=
            joyPadpos.dx + joyPadBox.size.width - (bubbleBox.size.width / 2)) {
      setState(() {
        if (details.delta.dy > 0) {
          print("Dragging in +Y direction");

          joystickDirection = JoystickDirection.FORWARD;
        } else
          joystickDirection = JoystickDirection.REVERSE;

        bubblealignment = Alignment(offset.dx - (bubbleBox.size.width / 2),
            offset.dy - (bubbleBox.size.height / 2));
      });
    }
  }

  _intitialPoint() {
    this.joyPadBox = joyPadKey.currentContext?.findRenderObject() as RenderBox;
    this.joyPadpos = joyPadBox.localToGlobal(Offset.zero);
    this.bubbleBox =
        joystickBubbleKey.currentContext?.findRenderObject() as RenderBox;

    setState(() {
      joystickDirection = JoystickDirection.STABLE;
      bubblealignment = Alignment(
          joyPadpos.dx +
              (joyPadBox.size.width / 2) -
              (bubbleBox.size.width / 2),
          joyPadpos.dy +
              (joyPadBox.size.height / 2) -
              (bubbleBox.size.height / 2));
    });
  }

  initState() {
    super.initState();
    WidgetsBinding.instance?.addPostFrameCallback((d) {
      _intitialPoint();
    });
  }

  _tiltForwardShandow() {
    return BoxShadow(
        offset: Offset(0, 10.0),
        blurRadius: 5.0,
        spreadRadius: 2,
        color: Colors.grey.shade400);
  }

  _tiltBackwordShandow() {
    return BoxShadow(
        offset: Offset(0, -10.0),
        blurRadius: 5.0,
        spreadRadius: 2,
        color: Colors.grey.shade400);
  }

  BoxShadow _resolveTilt() {
    switch (joystickDirection) {
      case JoystickDirection.FORWARD:
        return _tiltForwardShandow();
        // TODO: Handle this case.
        break;
      case JoystickDirection.REVERSE:
        return _tiltBackwordShandow();
        // TODO: Handle this case.
        break;
      case JoystickDirection.LEFT:
        // TODO: Handle this case.
        break;
      case JoystickDirection.RIGTH:
        // TODO: Handle this case.
        break;
      case JoystickDirection.STABLE:
        return BoxShadow();
        // TODO: Handle this case.
        break;
    }
    return BoxShadow();
  }

  _joyStickBubble() {
    return Container(
        key: joystickBubbleKey,
        height: 50,
        width: 50,
        decoration: BoxDecoration(
            gradient: LinearGradient(
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
                colors: [
                  color.mix(Colors.black, .1),
                  color,
                  color,
                  color.mix(Colors.white, .5),
                ],
                stops: [
                  0.0,
                  .3,
                  .6,
                  1.0,
                ]),
            shape: BoxShape.circle,
            boxShadow: [
              _resolveTilt(),
              BoxShadow(color: Colors.grey.shade400),
              BoxShadow(
                  color: Colors.grey.shade300,
                  blurRadius: 12,
                  spreadRadius: -12)
            ]));
  }

  Widget draggable() {
    return Positioned(
      top: bubblealignment.y,
      left: bubblealignment.x,
      child: Container(
        height: 50,
        width: 50,
        child: _joyStickBubble(),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Transform(
        alignment: Alignment.center,
        transform: Matrix4.rotationY(0.1),
        child: AnimatedContainer(
          duration: Duration(seconds: 10),
          child: GestureDetector(
            onPanEnd: (details) {
              _intitialPoint();
            },
            onPanUpdate: (details) {
              _onDragStarted(details);
              // bubblealignment = Alignment(details.delta.dx, details.delta.dy);
              // if (details.delta.dx > 0) {
              //   print("Dragging in +X direction");
              // } else
              //   print("Dragging in -X direction");

              // if (details.delta.dy > 0) {
              //   print("Dragging in +Y direction");
              //   setState(() {
              //     joystickDirection = JoystickDirection.FORWARD;
              //   });
              // } else
              //   setState(() {
              //     joystickDirection = JoystickDirection.REVERSE;
              //   });
            },
            child: Stack(
              alignment: Alignment.center,
              children: [
                Container(
                    key: joyPadKey,
                    height: 200,
                    width: 200,
                    child: Column(
                      children: [
                        Container(
                            alignment: Alignment.topCenter,
                            child: Padding(
                              padding: EdgeInsets.symmetric(vertical: 40),
                              child: Text(
                                "Forwards",
                                style: TextStyle(
                                  shadows: <Shadow>[_resolveTilt()],
                                ),
                              ),
                            )),
                        Spacer(),
                        Container(
                            alignment: Alignment.topCenter,
                            child: Padding(
                              padding: EdgeInsets.symmetric(vertical: 40),
                              child: Text(
                                "Reverse",
                                style: TextStyle(
                                  shadows: <Shadow>[
                                    _resolveTilt(),
                                  ],
                                ),
                              ),
                            )),
                      ],
                    ),
                    decoration:
                        BoxDecoration(shape: BoxShape.circle, boxShadow: [
                      _resolveTilt(),
                      BoxShadow(color: Colors.grey.shade400),
                      BoxShadow(
                          color: Colors.grey.shade300,
                          blurRadius: 12,
                          spreadRadius: -12)
                    ])),
                // Container(
                //   alignment: Alignment.center,
                //   height: 200,
                //   width: 200,
                //   child: Stack(
                //     children: [
                //       Align(
                //           alignment: Alignment.topCenter,
                //           child: Icon(Icons.keyboard_arrow_up)),
                //       Align(
                //           alignment: Alignment.centerRight,
                //           child: Icon(Icons.keyboard_arrow_right)),
                //       Align(
                //           alignment: Alignment.centerLeft,
                //           child: Icon(Icons.keyboard_arrow_left)),
                //       Align(
                //           alignment: Alignment.bottomCenter,
                //           child: Icon(Icons.keyboard_arrow_down)),
                //     ],
                //   ),
                // ),
                Container(
                    height: 30,
                    width: 200,
                    decoration: BoxDecoration(shape: BoxShape.rectangle,
                        // borderRadius: BorderRadius.circular(25),
                        boxShadow: [
                          _resolveTilt(),
                          BoxShadow(color: Colors.grey.shade400),
                          BoxShadow(
                              color: Colors.white,
                              blurRadius: 6,
                              spreadRadius: -1)
                        ])),
                draggable(),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

extension ColorUtils on Color {
  Color mix(Color? another, double amount) {
    return Color.lerp(this, another, amount)!;
  }
}

Joystick

Custom Joystick