I want to build JoystickView like this:
I ended up with this design:
Here is my code:
JoystickView(
showArrows: true,
innerCircleColor: Color(0xFF9510AC),
backgroundColor: Color(0xFF2D9BF0),
//size: 100,
size: MediaQuery.of(context).size.width * 0.45,
),
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)!;
}
}