기본 상호작용 - GestureDetector

웹 애플리케이션에서 사용자 상호작용은 필수적입니다. Flitter는 GestureDetector 위젯을 통해 다양한 사용자 입력을 간단하게 처리할 수 있도록 지원합니다.

GestureDetector란?

GestureDetector는 자식 위젯에 대한 다양한 제스처를 감지하는 위젯입니다. 클릭, 더블클릭, 호버, 드래그 등 다양한 사용자 입력을 처리할 수 있습니다.

기본 사용법

GestureDetector({
  onClick: () => {
    console.log('클릭되었습니다!');
  },
  child: Container({
    width: 100,
    height: 100,
    color: '#3B82F6',
    child: Center({
      child: Text('클릭하세요')
    })
  })
})

실전 예제

GestureDetector 활용하기

클릭 카운터, 호버 효과, 드래그 앤 드롭 등 다양한 상호작용을 구현해보세요.

클릭 카운터

GestureDetector({
  onClick: () => {
    this.setState(() => {
      this.count++;
    });
  },
  child: Container({
    // 클릭할 수 있는 영역
  })
})

호버 효과

GestureDetector({
  onMouseEnter: () => {
    this.setState(() => {
      this.isHovered = true;
    });
  },
  onMouseLeave: () => {
    this.setState(() => {
      this.isHovered = false;
    });
  },
  child: Container({
    color: this.isHovered ? '#10B981' : '#6B7280'
  })
})

드래그 앤 드롭

🎆 Draggable 위젯 사용 (추천)
// 1. import에서 Draggable 가져오기
import { Draggable } from '@meursyphus/flitter';

// 2. 간단한 사용법
Draggable({
  onDragUpdate: ({ delta }) => {
    this.setState(() => {
      this.position = {
        x: delta.x,  // 전체 이동 거리
        y: delta.y
      };
    });
  },
  child: Container({
    width: 60,
    height: 60,
    decoration: new BoxDecoration({
      color: '#8B5CF6',
      borderRadius: BorderRadius.circular(30)
    })
  })
})
🔧 Draggable의 내부 구현 (참고용)
// Draggable은 내부적으로 이렇게 구현되어 있음
build(context: BuildContext): Widget {
  return Transform.translate({
    offset: this.delta,  // 자동 위치 계산
    child: GestureDetector({
      onDragStart: this.handleMouseDown,
      onDragMove: this.handleMouseMove,  
      onDragEnd: this.handleMouseUp,
      child: this.widget.feedback
    })
  });
}

// GestureDetector를 사용하여 구현
✨ Draggable의 장점
  • Transform 자동 처리: 위치 계산과 이동을 내부에서 처리
  • 상태 관리: origin, delta, lastDelta 등을 자동 관리
  • Flutter 호환: Flutter의 Draggable과 동일한 API
  • 더 적은 코드: 복잡한 로직을 위젯이 대신 처리
🎯 제스처 타입
마우스 이벤트:
  • • onClick: 클릭 시
  • • onDoubleClick: 더블클릭 시
  • • onMouseEnter: 마우스 진입 시
  • • onMouseLeave: 마우스 벗어날 시
  • • onMouseMove: 마우스 이동 시
터치/드래그 이벤트:
  • • onPanStart: 드래그 시작
  • • onPanUpdate: 드래그 중
  • • onPanEnd: 드래그 종료
  • • onLongPress: 길게 누르기
  • • onTap: 탭 (모바일)
💡 직접 시도해보세요!
  • • GestureDetector는 StatefulWidget과 함께 사용하여 상태를 관리합니다
  • • 이벤트 핸들러 안에서는 반드시 setState를 사용하여 상태를 업데이트합니다
  • • Canvas 렌더러에서 더 나은 성능을 제공합니다
  • • 여러 GestureDetector를 중첩할 수 있습니다

StatefulWidget과 함께 사용하기

대부분의 상호작용은 상태 변화를 동반하므로, StatefulWidget과 함께 사용합니다:

class InteractiveButton extends StatefulWidget {
  createState() {
    return new InteractiveButtonState();
  }
}

class InteractiveButtonState extends State<InteractiveButton> {
  isPressed = false;
  isHovered = false;

  build(context: BuildContext) {
    return GestureDetector({
      onClick: () => {
        console.log('버튼 클릭!');
      },
      onMouseEnter: () => {
        this.setState(() => {
          this.isHovered = true;
        });
      },
      onMouseLeave: () => {
        this.setState(() => {
          this.isHovered = false;
        });
      },
      onPanStart: () => {
        this.setState(() => {
          this.isPressed = true;
        });
      },
      onPanEnd: () => {
        this.setState(() => {
          this.isPressed = false;
        });
      },
      child: Container({
        padding: EdgeInsets.symmetric({ horizontal: 24, vertical: 12 }),
        decoration: new BoxDecoration({
          color: this.isPressed 
            ? '#1E40AF' 
            : this.isHovered 
              ? '#2563EB' 
              : '#3B82F6',
          borderRadius: BorderRadius.circular(8)
        }),
        child: Text('인터랙티브 버튼', {
          style: new TextStyle({
            color: '#FFFFFF',
            fontWeight: 'bold'
          })
        })
      })
    });
  }
}

이벤트 종류 상세 설명

클릭 관련 이벤트

  • onClick: 클릭 시 발생
  • onDoubleClick: 더블클릭 시 발생
  • onLongPress: 길게 누를 때 발생

마우스 관련 이벤트

  • onMouseEnter: 마우스가 위젯 영역에 들어올 때
  • onMouseLeave: 마우스가 위젯 영역을 벗어날 때
  • onMouseMove: 마우스가 위젯 위에서 움직일 때

드래그 관련 이벤트

  • onPanStart: 드래그 시작 시
  • onPanUpdate: 드래그 중 (delta 값 제공)
  • onPanEnd: 드래그 종료 시

실용적인 활용 예제

1. 토글 스위치

class ToggleSwitch extends StatefulWidget {
  createState() {
    return new ToggleSwitchState();
  }
}

class ToggleSwitchState extends State<ToggleSwitch> {
  isOn = false;

  build(context: BuildContext) {
    return GestureDetector({
      onClick: () => {
        this.setState(() => {
          this.isOn = !this.isOn;
        });
      },
      child: Container({
        width: 60,
        height: 30,
        decoration: new BoxDecoration({
          color: this.isOn ? '#10B981' : '#6B7280',
          borderRadius: BorderRadius.circular(15)
        }),
        child: Stack({
          children: [
            AnimatedPositioned({
              duration: 200,
              left: this.isOn ? 30 : 0,
              child: Container({
                width: 30,
                height: 30,
                decoration: new BoxDecoration({
                  color: '#FFFFFF',
                  borderRadius: BorderRadius.circular(15)
                })
              })
            })
          ]
        })
      })
    });
  }
}

2. 드롭다운 메뉴

class DropdownMenu extends StatefulWidget {
  createState() {
    return new DropdownMenuState();
  }
}

class DropdownMenuState extends State<DropdownMenu> {
  isOpen = false;

  build(context: BuildContext) {
    return Column({
      children: [
        GestureDetector({
          onClick: () => {
            this.setState(() => {
              this.isOpen = !this.isOpen;
            });
          },
          child: Container({
            padding: EdgeInsets.all(12),
            decoration: new BoxDecoration({
              color: '#374151',
              borderRadius: BorderRadius.circular(6)
            }),
            child: Row({
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                Text('메뉴 선택', {
                  style: new TextStyle({ color: '#E5E7EB' })
                }),
                Icon(this.isOpen ? 'arrow_up' : 'arrow_down')
              ]
            })
          })
        }),
        if (this.isOpen) Container({
          margin: EdgeInsets.only({ top: 4 }),
          decoration: new BoxDecoration({
            color: '#374151',
            borderRadius: BorderRadius.circular(6)
          }),
          child: Column({
            children: ['옵션 1', '옵션 2', '옵션 3'].map(option => 
              GestureDetector({
                onClick: () => {
                  console.log(`선택: ${option}`);
                  this.setState(() => {
                    this.isOpen = false;
                  });
                },
                child: Container({
                  padding: EdgeInsets.all(12),
                  child: Text(option, {
                    style: new TextStyle({ color: '#E5E7EB' })
                  })
                })
              })
            )
          })
        })
      ]
    });
  }
}

성능 최적화 팁

  1. 적절한 렌더러 선택: 복잡한 상호작용이 많다면 Canvas 렌더러 사용
  2. 이벤트 디바운싱: 빈번한 이벤트(예: onMouseMove)는 디바운싱 고려
  3. 상태 업데이트 최적화: 불필요한 setState 호출 줄이기

접근성 고려사항

GestureDetector를 사용할 때는 접근성을 고려해야 합니다:

  1. 키보드 지원: 중요한 상호작용은 키보드로도 가능하게
  2. 포커스 표시: 포커스된 요소를 시각적으로 구분
  3. 충분한 터치 영역: 모바일에서 최소 44x44px 크기 확보

다음 단계

이제 Flitter의 기본기를 모두 익혔습니다! 핵심 개념에서 더 깊이 있는 내용을 학습하거나, 위젯 레퍼런스에서 다양한 위젯들을 살펴보세요.

요약

  • GestureDetector: 사용자 입력을 처리하는 핵심 위젯
  • StatefulWidget과 함께: 상태 변화를 위해 필수적으로 사용
  • 다양한 이벤트: 클릭, 호버, 드래그 등 지원
  • setState 사용: 이벤트 핸들러 내에서 상태 업데이트
  • 실용적 활용: 버튼, 토글, 드롭다운 등 구현 가능