Scroll View 만들기(1) - SingleChildScrollView
Scroll View 만들기(2) - CustomScrollView
Scroll View 만들기(3) - ListView
Scroll View 만들기(5) - GestureDetector + Stack 2편
지금까지 Flutter로 Scroll View를 생성하는 방법에 대해 간단하게 살펴보았다. 기본 Flutter Widget을 사용해서 만들어 보았는데, 이번 글에서는 직접 Scroll View를 구현하는 방법에 대해서 작성해 보도록 하겠다.
Scroll View를 커스텀으로 직접 만들어 보려면 어떤 구조로 뷰를 배치하여야 할것인지 부터 생각해 봐야한다. 저도 여러 방법들을 고민해보고 다양한 구조의 뷰를 배치해 보면서 Scroll View를 구현해 봤지만 아직까지 가장 좋은 방법은 이번 글에서 다루는 방법이라고 생각한다.
물론 더 좋은 방법이 분명히 있을 것인데, 앞으로 더 고민해 보면서 다른 방법들도 찾아볼 예정이다.
직접 개발한 Scroll View의 구현 정도를 보기 위해 ListView.builder와 직접 개발하는 Scroll View를 각각 배치하여 개발을 하였다.
내용이 길어져 2편으로 나눠서 작성하였고, 상태관리 위젯은 Get Reactive 방식을 사용하였다.
Scroll View를 직접 만들기 위해서 GestureDetector와 Stack 구조를 사용하여 만들었다.
Stack은 위젯을 Positioned 위젯으로 생성하면 top, bottom, left, right의 방향으로 포지션을 지정해 줄 수 있다. 여기서는 top의 포지션만 변경시켜 사용할 예정이다.
이제 화면을 스크롤할 때 사용자가 화면에서 제스쳐를 하는 동작을 수신하여야 하는데, 이럴 때 사용할 수 있는 위젯이 GestureDetector위젯이다.
GestureDetector 위젯은 터치, 길게 터치, 두번 터치, 수평, 수직 등의 다양한 제스쳐를 수신할 수 있는 위젯이다. 여기서는 수직 제스쳐만을 사용할 예정이다.
먼저 UI 구조를 잡아보자. Scffold를 사용해서 body부분을 Column 위젯으로 생성하여 자식 위젯들로 왼쪽 영역에는 직접 개발하는 Scroll View를 배치하고 오른쪽 영역에는 ListView.builder로 생성한 스크롤 뷰를 배치하기로 하였다.
상단에 각 영역을 구분하기 위한 UI이다.
SizedBox(
height: 60,
width: MediaQuery.of(context).size.width,
child: Wrap(
children: [
_tab(context: context, title: 'Gesture To Scroll'),
_tab(context: context, title: 'List View'),
],
),
),
SizedBox _tab({
required BuildContext context,
required String title,
}) {
return SizedBox(
width: MediaQuery.of(context).size.width / 2,
height: 60,
child: Center(
child: Text(
title,
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20),
)),
);
}
Expanded 위젯으로 나머지 영역만큼 확장하여 차지하게 해주고 Stack 구조로 생성하여 직접 개발하는 Scroll View에는 왼쪽 영역만 차지하게 하고 오른쪽 영역에 해당하는 ListView.builder 위젯은 right:0으로하여 오른쪽 영역으로 포지션을 잡아준다.
Expanded(
child: Stack(
children: [
...
...
]
)
)
ListView.builder 부분을 아래와 같이 생성해 준다.
Positioned(
right: 0,
child: Container(
color: Colors.transparent,
width: MediaQuery.of(context).size.width / 2,
height: MediaQuery.of(context).size.height,
child: ListView.builder(
itemCount: 1000,
itemBuilder: ((context, index) {
return SizedBox(
height: 50,
child: Center(
child: Text(
"Index : $index",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color: Colors.accents[index % 15]),
),
),
);
})),
),
),
자 이제 직접 만들 Scroll View에 해당되는 부분의 UI이다.
Reactive Get 방식의 상태 관리를 사용하기 위해 Obx 빌더로 만들어주고 Postioned 위젯을 리턴 한다.
Postioned 위젯의 top의 위치를 활용하여 스크롤을 구현할 예정이다. 간단하게 살펴보면 top의 위치가 0이면 화면에서 보이게 될것이고, -100이면 위젯의 높이 상단 100만큼은 마이너스 포지션으로 이동하여 뷰에서 보이지 않게 된다.
이것을 활용하여 제스쳐로 받아오는 값 만큼 top 포지션을 이동시켜 주면 스크롤 뷰를 생성할 수 있게된다.
Obx(() => Positioned(
top: -_controller.topPosition.value,
child : GestureDetector(
...
)
))
GestureDetector 위젯의 기능 중 아래 3개의 기능을 사용 할 예정이다.
onVerticalDragStart는 제스쳐가 수직 방향으로 최초 인식될 수신받는 기능을 가지고 있고, onVerticalDargEnd는 제스쳐를 놓았을 때 수신되는 기능이다. 그리고 onVerticalDragUpdate는 제스처를 진행하는 동안 지속적으로 수신 값을 받아올 수 있는 기능을 가지고 있다.
이렇게 3개의 기능을 사용하여 구현을 할 예정이다.
onVerticalDragStart: (details) {},
onVerticalDragUpdate: (details) {},
onVerticalDragEnd: (details) {},
직접 개발하는 Scroll View의 UI 부분의 코드이다. 여기서 한 가지 의문이 직접 Scoll View를 구현하고 있는데, SingleChildScrollView가 사용된 것이 보인다. SingleChildScrollView를 사용하지 않으면 overflow가 발생하기 때문에 SingleChildScrollView로 만들어주고, physics 값을 NeverScrollableScrollPhysics()로 주어 스크롤이 작동되지 않도록 해준다.
Obx(() => Positioned(
top: -_controller.topPosition.value,
child: GestureDetector(
onVerticalDragStart: (details) => _controller.dragStarted(details),
onVerticalDragUpdate: (details) => _controller.dragUpdated(details: details, min: 0, max: 50000),
onVerticalDragEnd: (details) => _controller.dragEnded(
details: details, min: 0, max: 50000),
child: Container(
color: Colors.transparent,
width: MediaQuery.of(context).size.width / 2,
child: SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
...List.generate(
1000,
(index) => SizedBox(
height: 50,
child: Text(
"Index : $index",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
color:
Colors.accents[index % 15]),
),
))
],
),
),
),
),
)),
이번 글에서는 직접 스크롤 뷰를 만들기 위해 Stack + GestureDetector를 사용하여 만드는 방법의 UI 부분만을 작성하였다. 상태 변경에 대한 부분은 다음 글에서 이어서 다루도록 하겠다.