inblog logo
|
jay0628
    Flutter

    [Flutter 심화] 1. 키보드가 올라올 때 버튼이 가려지는 문제

    김주희's avatar
    김주희
    Aug 04, 2025
    [Flutter 심화] 1. 키보드가 올라올 때 버튼이 가려지는 문제
    Contents
    핵심 포인트

    상황

    화면에 고정된 크기의 노란색 컨테이너와 그 아래에 TextFormField가 두 개와 로그인 버튼이 존재한다.
     
    문제는 텍스트 폼 필드에 값을 입력하기 위해 클릭하는 순간 키보드가 올라오면서 텍스트 폼 필드를 덮어버린다.
     
    목표는 세 번째 사진처럼 키보드가 텍스트 폼 필드와 로그인 버튼 영역을 가리지 않고 밀려 올라가도록 하는 것이다.
    notion image
    notion image
    notion image
     
     

    resizeToAvoidBottomInset

    • Scaffold의 속성
    • true일 때
      • 키보드가 올라오면 body 영역이 키보드 높이 만큼 줄어듦
    • false일 때
      • 키보드가 올라와도 body 영역 유지
      • 키보드에 가려질 수 있음

    Flexible

    • 자식 위젯이 가능한 만큼만 커지게 허용
    • cf) Expanded
      • 자식 위젯을 무조건 남은 공간만큼 꽉 채우도록 확장
      • 자식 위젯이 스스로 크기를 정할 수 없음
    • 현재 내 크기만큼 잡음
       
       
       

      ScrollController

      • 스크롤 동작 제어 및 관찰해주는 클래스
      • 스크롤 위치 제어
        • jumpTo() : 지정한 위치로 즉시 스크롤 이동
        • animateTo() : 부드럽게 지정 위치로 스크롤 애니메이션
      • 스크롤 위치 확인
        • position.pixels : 현재 스크롤 위치(픽셀 단위)
        • position.maxScrollExtent : 스크롤 가능한 최대 범위(=스크롤 가능한 가장 아래쪽 위치의 값)
      • 스크롤 이벤트 감지
        • addListener() : 스크롤이 변경될 때마다 콜백 실행
      • 스크롤 끝 감지 (무한 스크롤 구현 시 사용)
        • 현재 위치와 position.maxScrollExtent 비교해서 끝에 도달했는지 판단
       
       
      구조
      Scaffold 내부
      리스트뷰 내부
       
      Expanded로 잡으면 끝까지 확장되는 문제
      Flexible로 잡으면 끝까지 확장되는 문제
      shrinkWrap
       
       
      전체를 컬럼으로 잡은 이유
      → 리스트뷰로 하면 스크롤 가능하니까 버튼이 inset(사용불가능한) 영역이 아님 (resizeToAvoidBottomInset : true)
      → Expanded대신에 shrinkWrap: true로 하면 자기 크기만큼 잡음 → 스크롤이 안생김
       
      컬럼으로 잡아야 화면을 꽉 채움 - 리스트뷰로 잡으면 꽉 안채우고 버튼을 가림
      리스트뷰 + shrinkWrap으로 하면 스크롤이 안생김
      이때 flexible을 사용함
      flexible은 expanded와 똑같은 기능을 함
      flexible = 현재 내 크기만큼 잡음
      리스트뷰에는 크기가 없는데 shrinkWrap으로 크기 만들고 flexible로 만들면 안터진다.
      flexible은 근데 내 크기가 좁아지면 변한다.
       
      근데 위의 텍스트 폼필드를 잡으면 키보드가 아래쪽 텍스트 폼필드를 가리는 문제가 생김
      → 스크롤 이벤트를 주면 해결 가능하다.
       
      ScrollController는 리스트뷰의 스크롤 감지 가능함. →
      onTap의 타이밍은 키보드가 올라오기 전이기 때문에 키보드가 올라오는 걸 await를 통해 기다려야 됨 → 0.6초(디바이스 성능에 따라 달라진다.) 정도 기다렸다가 키보드 올라오고 나서 onTap이 동작하면 밀려 올라간다.
      ⇒ 제일 밑의 텍스트폼필드만 빼고 다 걸어주면 된다!
       
       

      핵심 포인트

      1. resizeToAvoidBottomInset: true
      • 키보드가 올라올 때 Scaffold가 바디를 위로 밀어 올려서 가림/오버플로우를 방지.
      1. 스크롤 가능한 영역 + 컨트롤러(ScrollController)
      • 입력 포커스가 하단일 때도 스크롤로 뷰를 따라가게 함.
      1. 레이아웃 분리: “스크롤 영역” vs “고정 버튼”
      • 입력 폼은 Flexible/Expanded + ListView(또는 SingleChildScrollView)
      • 제출 버튼은 스크롤 밖에 두고 SafeArea로 하단 안전영역 보장.
      1. 포커스 이동 시 스크롤 보정
      • Scrollable.ensureVisible(context, alignment: …) 또는 controller.animateTo(...)로 포커스 필드를 가리지 않게 이동.
      1. 키보드 높이만큼 패딩 보정(선택)
      • 스크롤 영역 또는 바텀 버튼에 AnimatedPadding(padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom)) 적용하면 부드럽게 피함.
       
       
      import 'package:flutter/material.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: HomePage(), ); } } class HomePage extends StatefulWidget { @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { final username = TextEditingController(); final password = TextEditingController(); final scroll = ScrollController(); @override Widget build(BuildContext context) { return Scaffold( resizeToAvoidBottomInset: true, body: Column( children: [ Flexible( child: ListView( controller: scroll, shrinkWrap: true, children: [ Container( color: Colors.yellow, height: 500, ), TextFormField( onTap: () async { await Future.delayed(Duration(milliseconds: 600)); scroll.jumpTo(scroll.position.maxScrollExtent); }, controller: username, ), TextFormField( onTap: () async {}, controller: password, ), ], ), ), SizedBox( width: double.infinity, child: ElevatedButton(onPressed: () {}, child: Text("로그인")), ), ], ), ); } }
      Share article

      jay0628

      RSS·Powered by Inblog