본문 바로가기
Flutter for Beginners

Flutter의 제스처(Gesture) 관련 위젯

by Andrew's Akashic Records 2025. 3. 11.
728x90

Flutter

Flutter의 제스처(Gesture) 관련 위젯

Flutter에서는 사용자의 터치, 스와이프, 탭, 길게 누르기 등의 입력을 감지하는 제스처(Gesture) 위젯을 제공합니다.
이러한 위젯을 활용하면 버튼, 드래그 가능한 UI, 애니메이션 등의 다양한 상호작용을 구현할 수 있습니다.

1. GestureDetector (가장 기본적인 제스처 감지 위젯)

GestureDetector는 사용자의 다양한 터치 이벤트를 감지할 수 있는 기본 위젯입니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: GestureExample(),
    );
  }
}

class GestureExample extends StatefulWidget {
  @override
  _GestureExampleState createState() => _GestureExampleState();
}

class _GestureExampleState extends State<GestureExample> {
  String _text = "탭하세요!";

  void _changeText() {
    setState(() {
      _text = "탭 감지됨!";
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("GestureDetector 예제")),
      body: Center(
        child: GestureDetector(
          onTap: _changeText, // 탭 제스처 감지
          child: Container(
            padding: EdgeInsets.all(20),
            color: Colors.blueAccent,
            child: Text(
              _text,
              style: TextStyle(fontSize: 24, color: Colors.white),
            ),
          ),
        ),
      ),
    );
  }
}
  • onTap을 사용하여 사용자가 탭하면 텍스트가 변경됨
  • GestureDetector를 감싸서 탭 제스처를 감지

2. GestureDetector의 다양한 속성

GestureDetector는 다양한 제스처를 감지할 수 있습니다.

GestureDetector(
  onTap: () => print("탭!"),            // 탭 감지
  onDoubleTap: () => print("더블 탭!"), // 더블 탭 감지
  onLongPress: () => print("길게 누름!"), // 길게 누르기 감지
  onPanUpdate: (details) => print("드래그 중: ${details.delta}"), // 드래그 감지
  onScaleUpdate: (details) => print("확대/축소 중: ${details.scale}"), // 핀치 줌 감지
  child: Container(
    color: Colors.amber,
    padding: EdgeInsets.all(20),
    child: Text("제스처 감지 테스트"),
  ),
)
이벤트 설명
onTap 사용자가 화면을 한 번 탭하면 실행
onDoubleTap 사용자가 화면을 두 번 탭하면 실행
onLongPress 화면을 길게 누르면 실행
onPanUpdate 화면을 드래그하면 실행 (details.delta로 움직인 거리 확인)
onScaleUpdate 두 손가락을 이용해 확대/축소하는 동작 감지

3. InkWell (버튼 같은 터치 효과)

InkWell은 버튼과 같은 터치 효과(Ripple Effect)를 제공하는 위젯입니다.

InkWell(
  onTap: () {
    print("InkWell 탭!");
  },
  child: Container(
    width: 100,
    height: 50,
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.circular(10),
    ),
    child: Center(child: Text("눌러보세요!", style: TextStyle(color: Colors.white))),
  ),
)
  • 터치 시 잔물결 효과가 나타남
  • 버튼과 비슷한 UI를 구현할 때 유용

4. Dismissible (스와이프하여 삭제)

Dismissible은 사용자가 좌우로 스와이프하여 항목을 삭제할 수 있는 위젯입니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SwipeToDeleteExample(),
    );
  }
}

class SwipeToDeleteExample extends StatefulWidget {
  @override
  _SwipeToDeleteExampleState createState() => _SwipeToDeleteExampleState();
}

class _SwipeToDeleteExampleState extends State<SwipeToDeleteExample> {
  List<String> items = ["아이템 1", "아이템 2", "아이템 3"];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("스와이프 삭제")),
      body: ListView.builder(
        itemCount: items.length,
        itemBuilder: (context, index) {
          return Dismissible(
            key: Key(items[index]), // 각 아이템을 고유하게 구별
            background: Container(color: Colors.red), // 스와이프 시 배경색
            onDismissed: (direction) {
              setState(() {
                items.removeAt(index); // 삭제
              });
            },
            child: ListTile(
              title: Text(items[index]),
            ),
          );
        },
      ),
    );
  }
}
  • 리스트 항목을 좌우로 스와이프하면 삭제됨
  • Dismissible은 Todo 리스트, 이메일 앱, 쇼핑 카트 등에 유용

5. Draggable (드래그 & 드롭)

Draggable을 사용하면 위젯을 드래그하고 다른 위치에 놓을 수 있음.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: DragDropExample(),
    );
  }
}

class DragDropExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("드래그 & 드롭")),
      body: Center(
        child: Draggable(
          data: "Flutter",
          feedback: Icon(Icons.flight, size: 100, color: Colors.blue), // 드래그 시 표시될 UI
          child: Icon(Icons.flight, size: 50, color: Colors.red), // 원래 UI
          childWhenDragging: Icon(Icons.flight, size: 50, color: Colors.grey), // 드래그 중일 때 변경될 UI
        ),
      ),
    );
  }
}
  • 아이콘을 드래그하면 크기가 커지고 파란색으로 변경됨
  • Draggable은 게임, UI 요소 이동, 맞춤형 드래그 앤 드롭 기능 구현에 활용 가능

Flutter의 제스처(Gesture) 관련 위젯 예제 코드

main.dart

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: GestureExampleScreen(),
    );
  }
}

class GestureExampleScreen extends StatefulWidget {
  const GestureExampleScreen({super.key});

  @override
  _GestureExampleScreenState createState() => _GestureExampleScreenState();
}

class _GestureExampleScreenState extends State<GestureExampleScreen> {
  String _gestureText = "제스처 테스트"; // 변경될 텍스트 상태
  double _boxX = 0; // 드래그할 박스의 X 좌표
  double _boxY = 0; // 드래그할 박스의 Y 좌표
  double _scale = 1.0; // 확대/축소 배율

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("제스처 관련 위젯 예제")),
      body: Column(
        children: [
          // 1. GestureDetector (탭, 더블탭, 길게 누르기)
          Expanded(
            child: Center(
              child: GestureDetector(
                onTap: () {
                  setState(() {
                    _gestureText = "탭 감지!";
                  });
                },
                onDoubleTap: () {
                  setState(() {
                    _gestureText = "더블 탭 감지!";
                  });
                },
                onLongPress: () {
                  setState(() {
                    _gestureText = "길게 누름 감지!";
                  });
                },
                child: Container(
                  padding: EdgeInsets.all(20),
                  decoration: BoxDecoration(
                    color: Colors.blue,
                    borderRadius: BorderRadius.circular(10),
                  ),
                  child: Text(
                    _gestureText,
                    style: TextStyle(color: Colors.white, fontSize: 18),
                  ),
                ),
              ),
            ),
          ),

          Divider(),

          // 2. Draggable (드래그 가능한 박스)
          Expanded(
            child: Stack(
              children: [
                Positioned(
                  left: _boxX,
                  top: _boxY,
                  child: GestureDetector(
                    onPanUpdate: (details) {
                      setState(() {
                        _boxX += details.delta.dx;
                        _boxY += details.delta.dy;
                      });
                    },
                    child: Container(
                      width: 80,
                      height: 80,
                      decoration: BoxDecoration(
                        color: Colors.red,
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: Center(
                        child: Text(
                          "드래그",
                          style: TextStyle(color: Colors.white),
                        ),
                      ),
                    ),
                  ),
                ),
              ],
            ),
          ),

          Divider(),

          // 3. Dismissible (스와이프 삭제)
          Expanded(
            child: ListView.builder(
              itemCount: 3,
              itemBuilder: (context, index) {
                return Dismissible(
                  key: Key("item_$index"),
                  direction: DismissDirection.endToStart, // 오른쪽 → 왼쪽 스와이프 가능
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: EdgeInsets.only(right: 20),
                    child: Icon(Icons.delete, color: Colors.white),
                  ),
                  onDismissed: (direction) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text("아이템 $index 삭제됨")),
                    );
                  },
                  child: ListTile(
                    title: Text("스와이프해서 삭제하기 (아이템 $index)"),
                  ),
                );
              },
            ),
          ),

          Divider(),

          // 4. ScaleGestureDetector (확대/축소)
          Expanded(
            child: Center(
              child: GestureDetector(
                onScaleUpdate: (details) {
                  setState(() {
                    _scale = details.scale.clamp(0.5, 3.0); // 최소 0.5배 ~ 최대 3배 확대 가능
                  });
                },
                child: Transform.scale(
                  scale: _scale,
                  child: Container(
                    width: 100,
                    height: 100,
                    decoration: BoxDecoration(
                      color: Colors.green,
                      borderRadius: BorderRadius.circular(10),
                    ),
                    child: Center(
                      child: Text(
                        "확대/축소",
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

1. GestureDetector (탭, 더블탭, 길게 누르기 감지)

GestureDetector(
  onTap: () {
    setState(() {
      _gestureText = "탭 감지!";
    });
  },
  onDoubleTap: () {
    setState(() {
      _gestureText = "더블 탭 감지!";
    });
  },
  onLongPress: () {
    setState(() {
      _gestureText = "길게 누름 감지!";
    });
  },
  child: Container(
    padding: EdgeInsets.all(20),
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.circular(10),
    ),
    child: Text(
      _gestureText,
      style: TextStyle(color: Colors.white, fontSize: 18),
    ),
  ),
),

사용자가 박스를 터치하면 onTap, onDoubleTap, onLongPress 이벤트에 따라 텍스트 변경

  • 버튼 클릭 이벤트
  • 더블 탭하여 좋아요 기능
  • 길게 눌러 메뉴 표시

2. Draggable (드래그)

GestureDetector(
  onPanUpdate: (details) {
    setState(() {
      _boxX += details.delta.dx;
      _boxY += details.delta.dy;
    });
  },
  child: Container(
    width: 80,
    height: 80,
    decoration: BoxDecoration(
      color: Colors.red,
      borderRadius: BorderRadius.circular(10),
    ),
    child: Center(
      child: Text(
        "드래그",
        style: TextStyle(color: Colors.white),
      ),
    ),
  ),
),

사용자가 박스를 드래그하면 위치가 이동함

  • 지도 드래그
  • 카드 이동

3. Dismissible (스와이프 삭제)

Dismissible(
  key: Key("item_$index"),
  direction: DismissDirection.endToStart,
  background: Container(
    color: Colors.red,
    alignment: Alignment.centerRight,
    padding: EdgeInsets.only(right: 20),
    child: Icon(Icons.delete, color: Colors.white),
  ),
  onDismissed: (direction) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("아이템 $index 삭제됨")),
    );
  },
  child: ListTile(
    title: Text("스와이프해서 삭제하기 (아이템 $index)"),
  ),
),

리스트 아이템을 오른쪽에서 왼쪽으로 스와이프하면 삭제됨

  • 이메일 삭제
  • 채팅 목록 삭제

4. ScaleGestureDetector (확대/축소)

GestureDetector(
  onScaleUpdate: (details) {
    setState(() {
      _scale = details.scale.clamp(0.5, 3.0);
    });
  },
  child: Transform.scale(
    scale: _scale,
    child: Container(
      width: 100,
      height: 100,
      decoration: BoxDecoration(
        color: Colors.green,
        borderRadius: BorderRadius.circular(10),
      ),
      child: Center(
        child: Text(
          "확대/축소",
          style: TextStyle(color: Colors.white),
        ),
      ),
    ),
  ),
),

핀치 줌(손가락으로 확대/축소) 가능

  • 이미지 확대
  • 지도 확대
728x90