Flutter

flutter) Scrollable.ensureVisible 모바일 키보드 자리 문제 해결

조충희 2025. 9. 9. 16:28

Flutter: 키보드에 가려지는 입력창 문제, Scrollable.ensureVisible로 해결

화면 하단 입력창이 키보드에 가려짐 → 사용자 경험 저해
Scrollable.ensureVisible 적용 → 사용자 경험 향상

기존 방식의 한계

  • ScrollController.animateTo 사용
  • 문제점
    • 부정확한 위치 선정: 실제 포커스된 입력창 보장 불가
    • 타이밍 불일치: 키보드 애니메이션과 스크롤 애니메이션 불일치

Scrollable.ensureVisible 소개

  • 특정 위젯의 BuildContext 기반 자동 스크롤 위치 조정 기능
  • 입력창이 화면 내 완전 표시 보장

구현 방법

  1. FocusNode 할당 → 입력창 포커스 감지
  2. initState에서 FocusNode 리스너 추가
  3. 리스너 콜백 내 Scrollable.ensureVisible 호출
  final _idFocusNode = FocusNode();
  final _passwordFocusNode = FocusNode();
  final _passwordConfirmFocusNode = FocusNode();
  final _emailFocusNode = FocusNode();
  final _verificationCodeFocusNode = FocusNode();
  final _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();
    
    // 포커스 시 스크롤하여 해당 필드를 보이도록 하는 리스너 추가 함수
    void addEnsureVisibleListener(FocusNode node) {
      node.addListener(() {
        if (node.hasFocus) {
          // 키보드가 올라오고 레이아웃이 안정화될 시간을 주기 위해 짧은 지연 후 실행
          // 이 지연 시간(milliseconds)은 앱의 반응성 및 테스트를 통해 조절할 수 있습니다.
          Future.delayed(const Duration(milliseconds: 300), () {
            // 위젯이 여전히 마운트 상태이고, FocusNode의 context가 유효한지 확인
            if (mounted && node.context != null) {
              Scrollable.ensureVisible(
                node.context!, // 필수: 포커스된 위젯의 BuildContext
                duration: const Duration(milliseconds: 250), // 스크롤 애니메이션 지속 시간
                curve: Curves.easeInOut, // 스크롤 애니메이션 커브
                alignment: 0.1, // 정렬 옵션: 0.0은 위젯 상단, 0.5는 중앙, 1.0은 하단
                                // 0.1은 위젯이 뷰포트 상단에서 10% 위치에 오도록 (약간의 여백)
              );
            }
          });
        }
      });
    }

    // 각 포커스 노드에 리스너 할당
    addEnsureVisibleListener(_idFocusNode);
    // ... (다른 FocusNode들에도 동일하게 리스너 할당) ...
    // addEnsureVisibleListener(_passwordFocusNode);
    // addEnsureVisibleListener(_emailFocusNode);
    // addEnsureVisibleListener(_verificationCodeFocusNode);
  }

  @override
  void dispose() {
    // ... 컨트롤러 및 FocusNode들 해제 ...
    _idFocusNode.dispose();
    _scrollController.dispose();
    super.dispose();
  }

주요 포인트

  • Future.delayed: 키보드 애니메이션 완료 대기, 지연 시간은 테스트 후 조정 필요
  • mounted && node.context != null: 위젯 제거, FocusNode 무효 상황 대비 안전장치
  • alignment: Scrollable.ensureVisible의 alignment 매개변수 활용, 입력창 표시 위치 조정
    • 0.0 → 뷰포트 상단
    • 0.5 → 중앙
    • 0.1 → 상단 여유 공간 확보

Scrollable.ensureVisible 장점

  • 정확성: 특정 위젯의 가시성 보장
  • 부드러운 경험: duration, curve 지정 통한 자연스러운 스크롤
  • 자동 복구: 키보드 종료 시 스크롤 위치 복원 (Flutter 기본 동작 기반)

1. 입력창에 포커스를 주면 해당 입력창에 맞춰서 스크롤이 올라간다. 2. 키보드가 없어지면 스크롤도 원래대로 돌아간다.