![[Flutter 심화] 1. 키보드가 올라올 때 버튼이 가려지는 문제](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255BFlutter%2520%25EC%258B%25AC%25ED%2599%2594%255D%25201.%2520%25ED%2582%25A4%25EB%25B3%25B4%25EB%2593%259C%25EA%25B0%2580%2520%25EC%2598%25AC%25EB%259D%25BC%25EC%2598%25AC%2520%25EB%2595%258C%2520%25EB%25B2%2584%25ED%258A%25BC%25EC%259D%25B4%2520%25EA%25B0%2580%25EB%25A0%25A4%25EC%25A7%2580%25EB%258A%2594%2520%25EB%25AC%25B8%25EC%25A0%259C%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3Djay0628&w=2048&q=75)
Contents
핵심 포인트상황
화면에 고정된 크기의 노란색 컨테이너와 그 아래에 TextFormField가 두 개와 로그인 버튼이 존재한다.
문제는 텍스트 폼 필드에 값을 입력하기 위해 클릭하는 순간 키보드가 올라오면서 텍스트 폼 필드를 덮어버린다.
목표는 세 번째 사진처럼 키보드가 텍스트 폼 필드와 로그인 버튼 영역을 가리지 않고 밀려 올라가도록 하는 것이다.



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이 동작하면 밀려 올라간다.
⇒ 제일 밑의 텍스트폼필드만 빼고 다 걸어주면 된다!
핵심 포인트
resizeToAvoidBottomInset: true
- 키보드가 올라올 때
Scaffold
가 바디를 위로 밀어 올려서 가림/오버플로우를 방지.
- 스크롤 가능한 영역 + 컨트롤러(
ScrollController
)
- 입력 포커스가 하단일 때도 스크롤로 뷰를 따라가게 함.
- 레이아웃 분리: “스크롤 영역” vs “고정 버튼”
- 입력 폼은
Flexible/Expanded + ListView(또는 SingleChildScrollView)
- 제출 버튼은 스크롤 밖에 두고
SafeArea
로 하단 안전영역 보장.
- 포커스 이동 시 스크롤 보정
Scrollable.ensureVisible(context, alignment: …)
또는controller.animateTo(...)
로 포커스 필드를 가리지 않게 이동.
- 키보드 높이만큼 패딩 보정(선택)
- 스크롤 영역 또는 바텀 버튼에
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