flutterdartcontrolsjoystick

How can I design such custom JoystickView in Flutter?


I want to build JoystickView like this:

enter image description here

I ended up with this design:

enter image description here

Here is my code:

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

Solution

  • A very humble and kind-hearted soul Day Vy helped me giving the design with the code. I really appreciate his effort. God bless him. Here is the code and Screen record.

    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