
안녕하세요, 성조입니다.
이번 포스팅은 SingleChildScrollView에 대해 정리하는 시간을 가져보려 해요.
Flutter 공식 문서에서 사용되는 widgets.dart 안의 ScrollView class에 있는 개념 내용이에요.
포스팅에 사용된 문서 내용은 참조 자료로 하단에 남겨놨으니 조금 더 정확하게 이해하기를 원하는 분들은 하단 링크를 참조해주시면 감사드리겠습니다.
선 요약 -> 스크롤 UI 보다는 오버 플로우 문제를 방지하기 위한 단일 위젯으로 문제가 발생되는 것을 최소화 하기 위해 사용되는 것이 크다.
즉, 안전성을 높이기 위해 해당 클래스 사용을 추천하는 것이며, 한번에 많은 데이터를 마운트/페인트하지 않는 영역 또는 담당할 UI 위젯에서 사용을 권장한다. 긴 목록이나, 대용량 데이터의 경우 ListView와 같은 더 효율적인 접근 방법을 권장한다.
해당 스크롤 위젯이 주는 제약 충돌 등이 발생될 수 있기 때문에 실제 값을 개선하기 위해서는 최소 높이 부여나 별도의 IntrinsicHeight를 활용하는 등 상황에 맞게 옵션을 사용하는 것을 권장하고 있다.
SingleChildScrollView이란?

싱글 차일드 스크롤 뷰 클래스에 대한 문서 처음 들어가면 위 이미지처럼 다음의 문구가 기다리고 있다.
"A box in which a single widget can be scrolled."
이해를 위해 조금 풀어내면 하나의 위젯(child)만을 스크롤 가능하게 감싸는 박스를 만들기 위한 용도로 사용 될 수 있다.
그리고, ScrollView + ListBody 조합으로 자주 사용되는데 의도하는 방향은 원래는 고정 레이아웃인데, 환경에 따라 깨질 수 있는 UI 값들을 가장 안전한 방법으로 감사는 패턴이라고 이해하면 좋다. (공식 문서 기준 안전성을 올린 클래스)
즉, 레이아웃의 안전성을 확보하기 위해 사용되는 스크롤뷰의 클래스라고 봐야한다.
Best, Worst 선택지
Best
- 설정 화면, 프로필 편집, 폼 UI 등에서 Column 기반의 한 덩어리 정도의 UI 위젯 들에 사용
- AlertDialog, 팝업 등에서 내용이 길어지면서 발생될 수 있는 오버플로우를 방지하기 위한 용도로 사용 됨
- 가로모드/분할화면처럼 예상치 못한 화면 크기 전환할 때도 화면에 따라갈 수 있도록 보여야 될 때
Worst
- 너무 많은 자식 위젯, Children을 넣는다면 ListView가 훨씬 효율적이기 때문에 싱글차일드스크롤뷰의 경우 비추천하고 있다.
즉, 하나의 스크롤에서 긴 목록이 되면 될 수록 추천되지 않는다는 의미이다.
(한마디 덧붙임) 한 페이지에서 많은 콘텐츠를 보여줄 경우 SigleChildScrollView는 한번에 자식 위젯들을 마운트 및 페인트 해주기 때문에 큰 데이터가 많이 존재하는 경우 클라이언트 또는 서버 측에서 부담을 가하게 된다.
기본 사용법
기본 사용법은 SingleChildScrollView + Column의 조합 형태로 사용 할 수 있다.
SingleChildScrollView(
padding: const EdgeInsets.all(20),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Title'),
SizedBox(height: 10),
TextField(),
SizedBox(height: 10),
ElevatedButton(onPressed: () {}, child: Text('Save')),
],
),
)
예제 코드를 정리하면 다음과 같이 이해할 수 있다.
SingleChildScrollView(
padding: const EdgeInsets.all(20),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
...
)
padding 얼마의 패딩을 줄 것인지를 의미한다.
설명을 위해 작성한 패딩 부분에는 all을 통해 작성해서 위, 아래, 좌, 우 모든 영역에 패딩을 20만큼 준다는 의미가 된다.
자식(child: Column) 위치에 넣지 않고 스크롤뷰 위젯에 부여하면 스크롤 끝까지 동일한 여백등을 주거나, 키보드가 올라왔을 때도 여백이 유지되는 장점 때문에 스크롤 뷰쪽에서 패딩 또는 마진을 줘서 조정하는 예제 또는 경우가 많이 존재한다.
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag는 스크롤을 드래그하면 키보드가 자동으로 내려가는 옵션이다. 해당 기능의 옵션을 확인하면 다음과 같다.
manual(기본값) -> 고정 -> 키보드가 자동으로 해제 안되고 유지되는 옵션이다.
onDrag(온드래그) -> 사용자가 스크롤 의도를 보이면 고정되어 있던 키보드가 해제되는 옵션이다.
crossAxisAlignment: CrossAxisAlignment.stretch
Column의 가로축(cross axis) 기준으로 자식들을 부모의 최대 너비로 늘려주는데 너무 길어지는 경우 위젯을 잘못 구성하면 오버플로우가 배치될 수 있기에 스크롤뷰의 폭이 영향주는 값들을 지정, ...으로 전환, 예외 처리를 잡아주는 것이 좋다.
UI 폼에서 자주 사용하는 설정이지만, 폼 설정은 옵션이 너무나도 많기 때문에 조합하기 나름으로 사용한다.
children안에 있는 기본적인 문법을 정리하면 다음과 같다.
1. Text('타이틀') -> 타이틀 처리하는 값
2. SizeBox(height: 10) -> 위아래 간격을 주는 값 -> 실무에서는 디자인 시스템을 구축할 때 별도 시스템 변수 값을 불러와서 치수를 일관적으로 교정하여 생산성 있게 작업하는 것이 더 좋다. 초기에 설계나 실무 적용, 사이드 프로젝트 등 다양한 곳에서 사용하기 어렵다면 천천히 하드 코딩 등으로 구성한 다음 서비스를 우선 제공하며 리팩터링 레벨에서 검증하는 것을 추천한다.
플러터가 네이티브 만큼의 성능을 기본적으로 제공하는 것이 아님을 인지하고 있는 것이 좋다.
3. TextField() -> 사용자 입력 필드를 받는 것이며, 스크롤뷰 클래스 값으로 부르지 않으면 화면에 오버플로우를 자주 마주치게 될 수 있다.
4. ElevatedButton(onPressed: () {}, child: Text('Save')) -> 액션 버튼이며, stretch 덕분에 가로 전체를 차지하는데 사용된다.
(위 예제에 언급했던 crossAxisAlignment: CrossAxisAlignment.stretch 값을 의미한다.)
공식문서 권장 내용 정리
SingleChildScrollView는 스크롤을 위해 child에게 거의 사실상 무한대에 가까운 스크롤 축 클래스를 제공하는 클래스이고, Column과 같이 사용되는 경우가 잦은데 공식 문서에서 두 값을 단순히 감싸는 것만 하면 레이아웃이 의도와 다르게 동작할 수 있기 때문에 여러 패턴 옵션들을 같이 권장하고 있다. (정리하면 -> Column은 보통 '가능한 크게' 커지려는 성향을 많이 가지고 있고, SingleChildScrollView는 스크롤을 위해 위젯(child)에게 'infinite height'한 값을 줄 수 있다)
1. 대부분은 화면에 딱 맞고, 공간이 남으면 가운데/간격 배치” (LayoutBuilder + ConstrainedBox) 옵션이 있다.
목표
1) 내용이 적을 때: 화면 높이를 “최소한” 채우고(mainAxisAlignment로 가운데/간격 배치)
2) 내용이 많을 때: 자연스럽게 세로로 길어지면서 스크롤
핵심 아이디어(공식 설명 요약) LayoutBuilder로 viewport 제약을 받고 ConstrainedBox로 minHeight를 viewport 높이로 걸어줘서 Column이 “너무 작아지지 않게” 만드는 것이 위 옵션의 목표이다.
다만 문서에서 주의 점으로는 다음과 같이 말해준다.
이 패턴에서는 Expanded/Flexible의 값과는 별로 도움이 안될 수 있다고 말한다.
이유는 viewport로 인해 내부에서 available space가 무한대로 취급할 수 있기 때문이다.
2. 내용이 짧으면 화면을 꽉 채우고, 길면 스크롤” (ConstrainedBox + IntrinsicHeight + Expanded)
위 1번 패턴에 추가 개선하는 패턴으로 IntrinsicHeight로 Column을 ‘내용 크기’에 딱 맞추도록 강제하고, 그 결과 viewport vs content 중 더 큰 쪽으로 컬럼 높이가 결정되게 만드는 방식이다.
이 패턴에서는 치수 비용등을 반드시 주는 것을 권장하는데 이유는 IntrinsicHeight 자체가 “추가 레이아웃 패스를 유발하는 비교적 비싼 위젯”이라고 공식 API 문서에 명시돼 있다. 가능하면 남용하지 않는 것이 좋고 많을 수록 디바이스 장비에 성능 저하를 발생시키게 된다. 많은 성능을 개선하기 위해서는 이런 포인트 등을 무시하지 않고 개선 포인트로 스크럼 미션을 부여해주는 것이 좋다.
문서에서 이야기 해주는 약간의 팁을 정리하면 “컬럼 내부 위젯 수를 작게 유지”하거나, 크기가 확정된 덩어리는 SizedBox로 고정해 intrinsic 계산이 과하게 커지지 않도록 유도할 수 있다. 그래도, 가능하면 제한적인 것이 더 좋을 것으로 보인다.
3. 다이얼로그/팝업에서의 정석 조합: SingleChildScrollView + ListBody
다트에서 다이얼로그는 기본적으로 내용 크기에 맞춰 자기 자신을 사이징하려고 하므로, 내용이 길면 오버플로우가 날 수 있다. 그렇기 때문에 AlertDialog의 공식 문서 내용에서도 content에 스크롤 위젯(예: SingleChildScrollView)을 고려하라고 안내한다.
그만큼 디바이스를 모두 컨트롤해서 경우의 수를 파악하기 어려울 때 권장되는 안전성 좋은 패턴이라 볼 수 있으며, ListBody가 SingleChildScrollView와 함께 쓰일 수 있다고 API 문서에서 이야기한다.
4. 스크롤 위치 유지(PageStorage)와 PageStorageKey
스크롤 뷰들은 세션 중 스크롤 위치를 PageStorage로 저장/복원하려고 시도한다.
SingleChildScrollView에서는 저장/복원을 끄고 싶으면 ScrollController.keepScrollOffset = false 를 설정라하고 설명해준다.
같은 화면(같은 route) 안에 스크롤러가 여러 개면 각자 유니크한 PageStorageKey를 사용하도록 권장하고 있다.
조금 뜬금 없기에 덧붙이면 공식 문서에서 keepScrollOffset은 ScrollController 생성자에서 기본값이 true이며, false로 하면 저장을 하지 않고 항상 initialScrollOffset으로 시작한다는 설명이 나온다. 그렇기에 위젯 컴포넌트들을 설계할 때 필요한 값들이나 스토리지 활용 하면서 스토리지 키값을 설계한다면 해당 부분들을 같이 고려해서 작업하는 것이 좋다.
자주 쓰는 생성자 옵션
SingleChildScrollView 생성자는 다음 옵션들외에 다양한 옵션을 제공하지만, 자주 볼 수 있는 옵션은 다음과 같다.
scrollDirection: 세로/가로 스크롤 축
reverse: 스크롤 방향 반전
padding: 내부 여백
controller: 스크롤 위치 제어/읽기/복원 관련
physics: 스크롤 감각(바운스/클램프 등)
primary: PrimaryScrollController 사용 여부(일반적으로 controller 직접 쓰면 주의)
keyboardDismissBehavior: 드래그로 키보드 내리기


더 많은 옵션들은 공식문서에서 설명하는 값들과 함께 예제가 많이 있으니 필요할 때 찾아서 같이 학습하는 것을 권장한다.
모두 다 배우고 넘어가기에는 너무 많은 문서양으로 인해 지치거나 헷갈리기 쉽기 때문에 공식 문서를 백과사전 처럼 사용하자.
SingleChildScrollView 결론
- SingleChildScrollView는 스크롤 UI의 목적이 아니라 오버플로우에서 버그가 나오지 않도록 하는 오버플로우 보험에 가까운 성향을 띄는 클래스이다. SingleChildScrollView는 단일 child를 스크롤 가능하게 만드는 위젯이기 때문에 스크롤 UI가 X보다는 목적이 오버 플로우 방지의 성향의 목적을 가지고 있는 클래스임을 인지하자.
그리고, 대부분 화면에 보여야 하지만, 화면이 작아질 때를 대비해 안전하게 스크롤로 탈출시키는 용도가 문서 피셜 정석이다.
- 긴 목록/대량 데이터는 ListView 같은 더 효율적인 도구가 우선이다.
- Column과 함께 쓰면 “무한 제약 충돌”이 생길 수 있으니, 공식 패턴(최소높이 부여 / IntrinsicHeight 활용)을 상황에 맞게 적용하는 것을 권장한다.
오랜만에 포스팅을 작성하고 있었는데 중간에 작성했던 글과 저장하기로 기록했던 내용들이 다 날아가서 새로 작성을 진행했네요..
이런 현상이 잦아지면 앞으로 티스토리에 꼬박꼬박 유의미하게 포스팅을 진행할 수 있을지 조금은 걱정도 됐네요..
다음 포스팅 때 뵙겠습니다.
감사합니다.
- 참조 주소 -
https://api.flutter.dev/flutter/widgets/SingleChildScrollView-class.html?utm_source=chatgpt.com
SingleChildScrollView class - widgets library - Dart API
A box in which a single widget can be scrolled. This widget is useful when you have a single box that will normally be entirely visible, for example a clock face in a time picker, but you need to make sure it can be scrolled if the container gets too small
api.flutter.dev
'Flutter' 카테고리의 다른 글
| [Flutter] ListView 기초 정리 (0) | 2026.01.03 |
|---|---|
| [Flutter] Flutter Padding class 이해하기 (0) | 2026.01.02 |
| [Flutter] AppLifecycleState 이론 한 스푼 (7) | 2025.08.04 |
| [Flutter] Bloc 상태 관리. 기초부터 심화까지 공식문서 학습하기 (1) | 2025.06.15 |
| [Flutter] StatelessWidget과 StatefulWidget의 차이점 정리 (0) | 2023.08.19 |