개요
Scroll Controller(스크롤 컨트롤러)를 이용하여 스크롤의 특정 위치로 이동하는 방법을 알아보자.
포스텅에서 설명하는 스크롤 컨트롤러는 위치 조정에 대한 메서드와 속성을 다룬다.
포스팅에서 다루는 프로젝트는 깃허브에서 다운 받을 수 있다.
Scroll Controller
설명 및 정의
스크롤 컨트롤러는 스크롤 가능한 위젯을 제어하는 기능을 한다.
주로 스크롤 위치 조정과 스크롤 시 애니메이션 제어 등의 기능을 제공한다.
스크롤 컨트롤러 선언 & 정의 방법은 아래와 같다.
ScrollController 변수명 = ScrollController();
생성 속성
- initialScrollOffset
- 초기 스크롤의 위치를 의미한다.
- 입력 타입은 double이다.
- keepScrollOffset
- 페이지가 이동되었을 경우 스크롤 위치 유지 여부를 설정한다.
- 입력 타입은 bool이다.
- 스크롤 위치는 PageStorage에 저장되어 해당 페이지로 다시 전환될 때 복원된다.
* PageStorage : 위젯이 삭제된 후 지속 상태를 선택할 수 있는 하위 트리를 설정한다. 위젯보다 오래 지속될 수 있는 값을 저장하고 복원하는데 사용하는 클래스이다.
// 속성 설정 예시
ScrollController scrollController = ScrollController(
initialScrollOffset: 100.0, // 초기 스크롤 위치는 100에서 시작
keepScrollOffset: true, // 페이지 이동 시 현재 스크롤 위치 유지
);
스크롤 위치 제어 메서드
이동 방법은 크게 두 가지로 나뉜다. 선호도에 맞게 사용하면 된다.
포스팅에서는 부드럽게 애니메이션을 주어 이동할 것이므로 animateTo 메서드를 사용하려 한다.
- jumpTo : 특정 위치로 즉시 이동한다.
- animateTo : 애니메이션 효과를 사용하여 특정 위치로 이동한다.
/* 스크롤 위치 제어 메서드 설정 예시 */
// jumpTo 메서드 : 스크롤의 위치를 100으로 즉시 이동
scrollController.jumpTo(100.0);
// animateTo 메서드 : 스크롤의 위치를 100으로 천천히 0.5초동안 애니메이션을 주며 이동
scrollController.animateTo(100.0,
duration: const Duration(milliseconds: 500), curve: Curves.ease);
샘플 코드 : 스크롤 제어하기
요구 사항
- 리스트의 내용을 스크롤 할 수 있어야 한다.
- 스크롤의 위치를 맨 위, 맨 밑 등으로 제어할 수 있어야 한다.
UI 설계
[ body ]
리스트 내에 표시할 내용
- ListView : 리스트를 표시하기 위한 위젯
- Container : 각각의 내용물의 영역을 표시하기 위한 위젯
- Text : 각각의 내용물의 내용을 표시하기 위한 위젯
- Container : 각각의 내용물의 영역을 표시하기 위한 위젯
[ fab ]
스크롤 위치 제어에 사용될 fab (플로팅 액션 버튼)
- Column : 플로팅 액션 버튼을 여러 개 생성하기 위한 위젯
- FloatingActionButton : 사용자와 상호작용하여 스크롤 제어 기능 수행을 위한 위젯
기능 설계
리스트의 내용은 총 10개로 제한하며, 각 리스트에는 1부터 10까지의 내용을 포함한다.
각각의 내용을 포함하는 위젯의 높이는 200으로 고정하며, 위 아래의 여백은 각각 50으로 정의한다.
(총 높이 300, 하나씩 리스트를 이동하기 위함)
스크롤을 제어하는 기능은 다음과 같이 분류한다.
- 맨 위로 이동 : 스크롤의 가장 위로 이동한다.
- 위로 이동 : 이전 내용물의 위치로 이동한다.
- 중간으로 이동 : 스크롤의 중앙의 위치로 이동한다.
- 아래로 이동 : 다음 내용물의 위치로 이동한다.
- 맨 아래로 이동 : 스크롤의 가장 아래로 이동한다.
- 특정 위치로 이동 : 지정한 특정 위치로 이동한다. 포스팅에서는 150의 위치로 지정하였다.
코딩
사용될 함수 선언
- 스크롤 컨트롤러
- 리스트 내용
ScrollController scrollController = ScrollController();
List value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
바디 영역과 fab 영역 나누기
- body : ListView를 표시할 영역
- floatingActionButton : 스크롤 제어 기능을 수행하는 버튼 표시 영역
@override
Widget build(BuildContext context) {
return Scaffold(
body: mainBody(),
floatingActionButton: fab(),
);
}
바디 영역
- 리스트뷰 빌더를 사용하여 리스트의 값을 인덱스를 이용하 차례대로 생성
- 가독성을 위해 컨테이너 영역을 위젯 함수로 분리하였음
컨테이너 영역
- 리스트뷰 빌더에서 넘겨받은 값을 이용하여 내용 구성
- 각 리스트마다 색상을 다루게 주어 시각적으로 식별할 수 있도록 함
// 바디 영역
Widget mainBody() {
return ListView.builder(
controller: scrollController,
itemCount: value.length,
itemBuilder: (context, index) {
return listContainer(value[index], index);
},
);
}
// 바디 영역의 리스트 컨테이너
Widget listContainer(value, index) {
return Container(
margin: const EdgeInsets.fromLTRB(0, 50, 0, 50),
width: double.infinity,
height: 200,
color: Colors.amber[(index + 1) * 100],
child: Center(
child: Text(
value.toString(),
style: const TextStyle(fontSize: 30),
),
),
);
}
fab 영역
- fab 위젯 선언 코드가 반복되므로 함수로 처리
위젯 생성 함수 (floatingActionButton)
- fab 위젯 생성
- 스크롤 제어 기능을 정의한 함수(activeScroll)를 호출하여 기능 수행
스크롤 제어 기능 정의 함수 (activeScroll)
- 기능을 수행하기 위한 함수
- 맨위로, 위로, 중간으로, 아래로, 맨아래로, 특정위치
- 스크롤 이동 메서드가 반복되어 사용하며 코드가 지저분해지므로 스크롤 이동 메서드를 따로 함수로 처리
스크롤 이동 정의 함수 (moveScroll)
- animateTo 메서드를 이용하여 특정 위치로 이동하는 기능을 수행하는 함수
// 플로팅 액션 버튼 구성
Widget fab() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
floatingActionButton("맨위로"),
floatingActionButton("위로"),
floatingActionButton("중간으로"),
floatingActionButton("아래로"),
floatingActionButton("맨아래로"),
floatingActionButton("특정위치"),
],
);
}
// 플로팅 액션 버튼 위젯
Widget floatingActionButton(String text) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
onPressed: () {
activeScroll(text);
},
child: Text(text)),
);
}
// 플로팅 액션 버튼 기능 정의
void activeScroll(text) {
if (text == "맨위로") {
moveScroll(0);
} else if (text == "위로") {
moveScroll(scrollController.offset - 300);
} else if (text == "중간으로") {
moveScroll(scrollController.position.maxScrollExtent / 2);
} else if (text == "아래로") {
moveScroll(scrollController.offset + 300);
} else if (text == "맨아래로") {
moveScroll(scrollController.position.maxScrollExtent);
} else if (text == "특정위치") {
moveScroll(150);
}
}
// 스크롤 이동 기능 정의
void moveScroll(value) {
scrollController.animateTo(value.toDouble(),
duration: const Duration(milliseconds: 500), curve: Curves.ease);
}
전체 소스 코드
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: MainPage(),
),
);
}
class MainPage extends StatefulWidget {
const MainPage({super.key});
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
ScrollController scrollController = ScrollController();
List value = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
@override
Widget build(BuildContext context) {
return Scaffold(
body: mainBody(),
floatingActionButton: fab(),
);
}
// 바디 영역
Widget mainBody() {
return ListView.builder(
controller: scrollController,
itemCount: value.length,
itemBuilder: (context, index) {
return listContainer(value[index], index);
},
);
}
// 바디 영역의 리스트 컨테이너
Widget listContainer(value, index) {
return Container(
margin: const EdgeInsets.fromLTRB(0, 50, 0, 50),
width: double.infinity,
height: 200,
color: Colors.amber[(index + 1) * 100],
child: Center(
child: Text(
value.toString(),
style: const TextStyle(fontSize: 30),
),
),
);
}
// 플로팅 액션 버튼 구성
Widget fab() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
floatingActionButton("맨위로"),
floatingActionButton("위로"),
floatingActionButton("중간으로"),
floatingActionButton("아래로"),
floatingActionButton("맨아래로"),
floatingActionButton("특정위치"),
],
);
}
// 플로팅 액션 버튼 위젯
Widget floatingActionButton(String text) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
onPressed: () {
activeScroll(text);
},
child: Text(text)),
);
}
// 플로팅 액션 버튼 기능 정의
void activeScroll(text) {
if (text == "맨위로") {
moveScroll(0);
} else if (text == "위로") {
moveScroll(scrollController.offset - 300);
} else if (text == "중간으로") {
moveScroll(scrollController.position.maxScrollExtent / 2);
} else if (text == "아래로") {
moveScroll(scrollController.offset + 300);
} else if (text == "맨아래로") {
moveScroll(scrollController.position.maxScrollExtent);
} else if (text == "특정위치") {
moveScroll(150);
}
}
// 스크롤 이동 기능 정의
void moveScroll(value) {
scrollController.animateTo(value.toDouble(),
duration: const Duration(milliseconds: 500), curve: Curves.ease);
}
}