플러터로 조이스틱을 구현해보겠습니다
참고: Getx 사용햇습니다

조이스틱 드래그 범위 지정 (진한 회색 원이 연한 회색 밖으로 나갈 수 없도록)
조이스틱의 중심과 현재 드래그 위치의 거리를 계산해서 드래그 위치가 조이스틱 배경 원의 반지름을 초과하면 드래그 위치를 조이스틱 배경 원 둘레에 맞게 조정하는 방식
class JoystickController extends GetxController {
// 조이스틱 중심점 위치
var center = Offset(0, 0).obs;
// 조이스틱 움직임 위치
var movement = Offset(0, 0).obs;
// 배경 원 반지름
double radius = 50;
updatePosition(DragUpdateDetails position) {
// 현재 드래그 위치와 초기 드래그 위치의 차이 계산해서 movement 변수에 저장
movement.value = position.localPosition - center.value;
// 현재 드래그 위치와 원점(조이스틱 중심) 간의 거리 계산
double distance = movement.value.distance;
// 만약 드래그 위치가 조이스틱의 원의 반지름을 초과하면
if (distance > radius) {
// 드래그 위치를 조이스틱의 원 둘레에 맞게 조정
movement.value = movement.value * (radius / distance);
}
}
}
조이스틱 GestureDetector 추가, Transform.translate
onPanStart : 사용자가 드래그 동작을 시작할 때 호출되는 콜백 함수onPanUpdate : 사용자가 드래그 동작을 진행하는 동안 호출되는 콜백 함수onPanEnd : 사용자가 드래그 동작을 마치고 손가락을 뗄 때 호출되는 콜백 함수Transform.translate : 하나의 위젯을 다른 위치로 이동시키는 데 사용되는 위젯// Obx 추가해야함
GestureDetector(
onPanStart: (details) {
controller.center.value = details.localPosition;
controller.movement.value = Offset(0, 0);
},
onPanUpdate: (details) {
controller.updatePosition(details);
},
onPanEnd: (details) {
controller.movement.value = Offset(0, 0);
},
child: CircleAvatar(
radius: controller.radius,
backgroundColor: Colors.grey[300],
child: Transform.translate(
offset: controller.movement.value,
child: CircleAvatar(
backgroundColor: Colors.grey[700],
),
),
),
)
GestureDetector 드래그
onPanStart: 사용자가 드래그 동작을 시작할 때 호출되는 콜백 함수
DragStartDetails객체를 인자로 받음
globalPosition: 드래그 시작 시점에서의 전체 화면 기준 위치를 나타내는Offset객체localPosition: 드래그 시작 시점에서의 이벤트가 발생한 위젯 내에서의 상대적인 위치를 나타내는 ****Offset객체onPanUpdate: 사용자가 드래그 동작을 진행하는 동안 호출되는 콜백 함수
DragUpdateDetails객체를 인자로 받음
globalPosition: 드래그 중인 현재 시점에서의 전체 화면 기준 위치를 나타내는Offset객체localPosition: 드래그 중인 현재 시점에서의 이벤트가 발생한 위젯 내에서의 상대적인 위치를 나타내는Offset객체delta: 드래그 중인 이전 위치(previousGlobalPosition)와 현재 위치(globalPosition)의 차이를 나타내는Offset객체onPanEnd: 사용자가 드래그 동작을 마치고 손가락을 뗄 때 호출되는 콜백 함수
DragEndDetails객체를 인자로 받음
velocity: 드래그 종료 시점의 속도를 나타내는Velocity객체, x축과 y축으로의 속도가 포함되어 있음
Transform.translate
하나의 위젯을 다른 위치로 이동시키는 데 사용되는 위젯
자식 위젯을 현재 위치에서 지정한 X축과 Y축의 픽셀만큼 이동시킬 수 있음
화면에서 자식 위젯의 위치를 재조정하고자 할 때 유용하게 사용됨
주요 속성
offset: 이동할 거리를 지정하는 속성Offset객체를 사용 /Offset(dx, dy)형식으로 X축과 Y축으로 이동할 거리를 지정할 수 있음
child: 이동시킬 대상이 되는 자식 위젯
updatePosition 메소드 내에 위치 이동하는 코드 추가class JoystickController extends GetxController {
...
// 조이스틱으로 움직이는 원(캐릭터) 위치
var circle = Offset(0, 0).obs;
...
updatePosition(DragUpdateDetails position) {
...
circle.value += movement.value * 0.1; // 0.1 곱해서 원 이동 속도 조절
}
}Obx(
() => Stack(
children: [
Align(
alignment: Alignment.center,
child: Transform.translate(
offset: controller.circle.value,
child: CircleAvatar(backgroundColor: Colors.green)
),
),
Positioned(
left: 40,
bottom: 50,
child: GestureDetector(
onPanStart: (details) {
controller.center.value = details.localPosition;
controller.movement.value = Offset(0, 0);
},
onPanUpdate: (details) {
controller.updatePosition(details);
},
onPanEnd: (details) {
controller.movement.value = Offset(0, 0);
},
child: CircleAvatar(
radius: controller.radius,
backgroundColor: Colors.grey[300],
child: Transform.translate(
offset: controller.movement.value,
child: CircleAvatar(
backgroundColor: Colors.grey[700],
),
),
),
),
),
],
)
) 
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'joystick_controller.dart';
import 'main_page.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return GetMaterialApp(
initialBinding: BindingsBuilder(() {
Get.lazyPut(() => JoystickController(), fenix: true);
}),
home: MainPage(),
);
}
}import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'joystick_controller.dart';
class MainPage extends GetView<JoystickController> {
const MainPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ㅋㅋ')),
body: Center(
child: Obx(() {
return Column(
children: [
Expanded(
child: Stack(
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.center,
child: Transform.translate(
offset: controller.circle.value,
child: CircleAvatar(backgroundColor: Colors.green)
),
),
Positioned(
left: 40,
bottom: 50,
child: GestureDetector(
onPanStart: (details) {
controller.center.value = details.localPosition;
controller.movement.value = Offset(0, 0);
},
onPanUpdate: (details) {
controller.updatePosition(details);
},
onPanEnd: (details) {
controller.movement.value = Offset(0, 0);
},
child: CircleAvatar(
radius: controller.radius,
backgroundColor: Colors.grey[300],
child: Transform.translate(
offset: controller.movement.value,
child: CircleAvatar(
backgroundColor: Colors.grey[700],
),
),
),
),
),
],
),
),
],
);
}),
),
);
}
}import 'package:flutter/material.dart';
import 'package:get/get.dart';
class JoystickController extends GetxController {
// 조이스틱 중심점 위치
var center = Offset(0, 0).obs;
// 조이스틱 움직임 위치
var movement = Offset(0, 0).obs;
// 조이스틱으로 움직이는 원 위치
var circle = Offset(0, 0).obs;
// 배경 원 반지름
double radius = 50;
updatePosition(DragUpdateDetails position) {
// 현재 드래그 위치와 초기 드래그 위치의 차이 계산해서 movement 변수에 저장
movement.value = position.localPosition - center.value;
// 현재 드래그 위치와 원점(조이스틱 중심) 간의 거리 계산
double distance = movement.value.distance;
// 만약 드래그 위치가 조이스틱의 원의 반지름을 초과하면
if (distance > radius) {
// 드래그 위치를 조이스틱의 원 둘레에 맞게 조정
movement.value = movement.value * (radius / distance);
}
circle.value += movement.value * 0.1;
}
}