-
[Flutter] ScrollController를 통한 자동 스크롤 구현IT 2025. 5. 27. 17:23
안녕하세요. 오늘은 flutter로 개발한 FAQ 화면을 개선하는 과정을 공유하려고 합니다~
기존 코드에 문제점이 있는데요.
FAQ를 펼칠 때 내용이 화면 아래로 가려져서 보이지 않는 문제 입니다.
즉, 화면을 펼칠때 그 내용이 하단에 있을때 그냥 화면 밖으로 펼쳐져서 스크롤해야만 확인할 수 있는 문제죠.
화면을 캡쳐해서 보여드릴 수 없는 점 양해부탁드립니다.
아무튼! 리스트 하단에 있는 FAQ를 펼치면 답변이 화면 아래로 가려져서 사용자가 스크롤을 직접 해야 하는 불편함이 있었습니다.
일단 화면의 state를 보면 기존을 다음과 같이 되어있었습니다.
Before 👎
class FaQScreenState extends State<FaQScreen> { List<FAQ> models = []; FAQ? selectedModel; // ScrollController 없음 }
After 👍
class FaQScreenState extends State<FaQScreen> { List<FAQ> models = []; FAQ? selectedModel; final ScrollController _scrollController = ScrollController(); // 추가 @override void dispose() { _scrollController.dispose(); // 메모리 해제 super.dispose(); } }
다음과 같이 scroll controller를 추가해줍니다. 그리고 dispose할때 메모리 해제는 잊으면 안됩니다.
ListView.separated( controller: _scrollController, // 👈 여기서 연결! itemBuilder: (context, index) {
LisetViewd에 _scrollController를 추가해준 이유는 이게 없다면 스크롤 위치를 제어할 수 없기 때문입니다.
그리고 자동 스크롤 기능을 구현하지 못합니다.
자동 스크롤 기능이 없었기 때문에 이제 faq에서 답변을 펼칠 때, 자동 스크롤이 되도록 구현해보겠습니다.
Before 👎
onTap: () { if(selectedModel!=model){ selectedModel= model; }else{ selectedModel=null; } setState(() {}); // 스크롤 기능 없음 },
기존에는 다음과 같이 그냥 클릭 시에 값이 있으면 펼쳐지게 하는 것입니다.
After 👍
onTap: () => _onItemTap(model, index), // 새로 추가된 메서드 void _onItemTap(FAQ model, int index) { if (selectedModel != model) { selectedModel = model; setState(() {}); // ✨ 마법의 자동 스크롤! WidgetsBinding.instance.addPostFrameCallback((_) { _scrollToItem(index); }); } else { selectedModel = null; setState(() {}); } }
일단 어떻게 수정됐는지 하나씩 살펴보겠습니다.
void *onItemTap(FAQ model, int index)
FAQ 모델과 인덱스를 받아서 아이템 탭 이벤트를 처리합니다.
if (selectedModel != model)
현재 선택된 모델과 탭한 모델이 다른 경우 즉, 새로운 아이템을 선택한 경우
selectedModel = model; setState(() {});
선택된 모델을 업데이트하고 화면을 다시 그립니다.
WidgetsBinding.instance.addPostFrameCallback((*) { _scrollToItem(index); });
이후 addPostFrameCallback을 통해 현재 프레임이 완료된 후에 콜백을 실행 시켜주고요.
_scrollToItem(index)를 통해 해당 인덱스의 아이템으로 스크롤 이동을 합니다.
else { selectedModel = null; setState(() {}); }
새로운 아이템이 아닌 같은 아이템을 다시 탭한 경우에는 선택을 해제하고 화면을 업데이트합니다.
여기에는
- 토글 기능: 같은 아이템을 두 번 탭하면 선택 해제
- 자동 스크롤: 새 아이템 선택 시 해당 위치로 자동 스크롤
- PostFrameCallback 사용: UI 업데이트가 완료된 후 스크롤을 실행하여 부드러운 동작 보장
여기서 PostFrameCallback을 사용하는 WidgetsBinding은 Flutter의 코어 바인딩 클래스로, 위젯 프레임워크와 flutter 엔진 사이의 연결고리 역할을 합니다.
import 'package:flutter/widgets.dart';
위의 코드를 import를 추가하시면 사용할 수 있습니다.
"WidgetsBinding" 이것을 그럼 언제 사용할까요?
- PostFrameCallback: UI 업데이트 후 추가 작업 (스크롤, 포커스 등)
- 앱 상태 관리: 백그라운드/포그라운드 전환 감지
- 시스템 이벤트: 키보드, 화면 회전 등
다음과 같을 때 사용합니다.
마지막으로 _scrollToItem 이 함수가 남았는데요.
그리고 스마트 스크롤 로직을 주석과 함께 보여드리겠습니다.
void _scrollToItem(int index) { final RenderBox? renderBox = context.findRenderObject() as RenderBox?; if (renderBox == null) return; final screenHeight = MediaQuery.of(context).size.height; final itemHeight = 60.0; // FAQ 아이템 높이 final expandedHeight = 120.0; // 펼쳐진 답변 높이 final itemPosition = index * (itemHeight + 1); final totalItemHeight = itemHeight + expandedHeight; // 현재 스크롤 위치 계산 final currentScrollOffset = _scrollController.offset; final visibleAreaEnd = currentScrollOffset + screenHeight - 200; // 펼쳐진 내용이 화면 아래로 벗어나면 자동 스크롤 if (itemPosition + totalItemHeight > visibleAreaEnd) { final targetOffset = itemPosition + totalItemHeight - screenHeight + 250; _scrollController.animateTo( targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent), duration: const Duration(milliseconds: 300), // 부드러운 애니메이션 curve: Curves.easeInOut, ); } }
일단 현재 화면에 보이는 영역을 계산하고, 펼쳐질 내용이 화면을 벗어나는지 확인합니다.
이후 벗어나면 적절한 위치로 부드럽게 스크롤하도록 합니다.
여기서 이제 한 것들을 정리하면
1. 사용자 경험을 개선하였다.
2. 부드러운 애니메이션을 추가하여 자연스럽게 연출하였다.
3. dispose를 통해 메모리 관리도 하였다.
추가로 나머지 코드들에 const를 추가하여 flutter 위젯 트리에서 불필요한 재빌드를 방지해서 성능을 향상 시켰습니다.
배운 점!
- 작은 개선도 큰 차이를 만든다: const 키워드 하나만 추가해도 성능이 향상됩니다.
- 사용자 관점에서 생각하기: 개발자는 괜찮다고 생각해도 사용자에게는 불편할 수 있습니다.
- 코드 정리의 중요성: lint 규칙을 따르면 코드 품질이 자연스럽게 향상됩니다.
참고 문헌
https://api.flutter.dev/flutter/widgets/ScrollController-class.html
ScrollController class - widgets library - Dart API
Controls a scrollable widget. Scroll controllers are typically stored as member variables in State objects and are reused in each State.build. A single scroll controller can be used to control multiple scrollable widgets, but some operations, such as readi
api.flutter.dev
https://docs.flutter.dev/perf/rendering-performance
Improving rendering performance
How to measure and evaluate your app's rendering performance.
docs.flutter.dev
'IT' 카테고리의 다른 글
검색에 잘 걸리게 하고 싶어서 찾아본 SEO와 AEO 정리 (0) 2025.05.29 [Flutter] 안드로이드 앱 심사 올리는 절차 소개 (0) 2025.05.29 [Flutter] Bottom Navigation Bar 클릭 범위 문제 해결기 – InkWell과 Expanded의 조합 (0) 2025.04.22 [Flutter] 상태 변수 최적화 - late final 하나로 코드가 안정적 (0) 2025.04.22 [유지보수] 레거시 코드 리팩토링.. (0) 2025.04.22