DraggableScrollableSheet class
드래그 제스처 에 응답하여 스크롤 가능한 요소의 크기를 제한에 도달할 때까지 조절한 다음 스크롤하는 스크롤 가능한 요소의 컨테이너입니다.
공식 문서 코드
우리가 흔히 여러 프로그램들을 쓰다 보면 느낄 수 있는 공통적인 사항들이 있다. 바로 크기를 사용자가 직접 UI 의 크기를 설정할 수 있다는 건데 벌써 비주얼 스튜디오 코드나 안드로이드 스튜디오만 보더라도 툴바라든지 여러 화면의 요소들의 크기를 직접 조절할 수 있게 되어 있다. 이러한 기능을 구현려면 크기 조절에 유연한 DraggableScrollableSheet 위젯을 사용해 줄 수 있다.
이 DraggableScrollableSheet 위젯은 화면의 일부분으로부터 사용자가 드래그하여 확장하거나 축소할 수 있는 스크롤 가능한 시트(sheet)를 구현할 수 있도록 하는 위젯이다. 보통 BottomSheet과 비슷하지만 좀 더 유연하게 드래그와 스크롤을 결합한 UI를 구현할 수 있게 된다. 바로 한번 알아보자.
하위 속성
속성명 | 타입 | 기본값 | 설명 |
initialChildSize | double | 0.5 | 초기 높이를 부모의 높이에 대한 비율로 지정 |
minChildSize | double | 0.25 | 최소 높이를 부모 높이의 비율로 지정 |
maxChildSize | double | 1.0 | 최대 높이를 부모 높이의 비율로 지정 |
expand | bool | true | 위젯이 부모 영역을 모두 차지할지 여부 |
snap | bool | false | 사용자가 손을 뗐을 때 snapSizes에 지정된 크기로 자동 이동할지 여부 |
snapSizes | List<double>? | 없음 | 스냅할 대상 높이 목록 (비율 값) |
snapAnimationDuration | Duration? | 없음 | 스냅 애니메이션의 지속 시간 지정 |
controller | DraggableScrollableController? | 없음 | 시트를 프로그래밍으로 제어할 수 있는 컨트롤러 |
shouldCloseOnMinExtent | bool | true | 최소 크기까지 축소되었을 때 부모가 시트를 닫아야 할지 여부 |
builder | ScrollableWidgetBuilder | 필수 | 스크롤 가능한 내용을 생성하는 빌더 함수 |
일단 DraggableScrollableSheet 의 기본적인 사용법을 알고 가자.
기본적으로 이 위젯은 하단에서 나오는 BottomSheet 의 형태를 가지고 있다. 그리고 또한 이름에서 알 수 있듯이 기본적으로 스크롤을 지원해 주는 위젯인데 보통 안에 ListView 같은 스크롤 위젯을 넣는 편이다.
DraggableScrollableSheet(
initialChildSize: 0.5,
minChildSize: 0.1,
maxChildSize: 0.8,
builder: (context, scrollController) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [BoxShadow(blurRadius: 5, color: Colors.black26)],
),
child: ListView.builder(
controller: scrollController,
itemCount: 30,
itemBuilder: (context, index) {
return ListTile(title: Text('아이템 $index'));
},
),
);
},
),
일단 이 위젯에는 초기 크기와 최소/최대 크기를 지정해 줄 수 있다. 타입은 double 이지만 값을 입력받은 그대로 화면에 반영되는 게 아니라 화면의 비율을 나타내주는 값이다. 예를 들어 0.1 이면 MediaQuery.sizeOf(context).height * 0.1 이 느낌인 거다.
initialChildSize: 0.5, // 시작 크기 : 화면의 50%
minChildSize: 0.1, // 최소 크기 : 화면의 10%
maxChildSize: 0.8, // 최대 크기 : 화면의 80%
또한 builder: 로 빌더 함수를 받는데 여기다가 이제 넣고 싶은 거 넣어주면 되겠다.
builder: (context, scrollController) {
여기서 이렇게 인자값으로 scrollController 를 넘겨주는데 이걸 안에 작설 할 리스트뷰같은 곳의 controller 에 넣어주면 알아서 적용되게 된다.

참고로 showModalBottomSheet() 안에 넣어서 일반적인 ModalBottomSheet 의 형식으로 나타나게 할 수 또 있다.
암튼 이제 이거 크기 조절 하는 방법을 살펴보자. 공식 문서 코드를 그대로 살펴볼 것이다.
일단 가장 먼저 볼 것은 조금 재밌는 코드인데 바로 이것이다.
bool get _isOnDesktopAndWeb =>
kIsWeb ||
switch (defaultTargetPlatform) {
TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.windows => true,
TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia => false,
};
딱 보면 감이 오지 않는가? 그렇다. 현재 플랫폼이 웹/데스크톱인지 판별하는 getter 인 것이다. 이제 이렇게 만든 건 나중에 controller 에 넣어주게 될 것이다.
double _sheetPosition = 0.5;
final double _dragSensitivity = 600;
그리고 시트의 현재 높이 비율과 드래그 감도 조절 상수를 미리 선언해 놓은 모습이다. 이제 이걸로 크기 조절을 할 것이다.
class Grabber extends StatelessWidget {
const Grabber({super.key, required this.onVerticalDragUpdate, required this.isOnDesktopAndWeb});
final ValueChanged<DragUpdateDetails> onVerticalDragUpdate;
final bool isOnDesktopAndWeb;
@override
Widget build(BuildContext context) {
if (!isOnDesktopAndWeb) {
return const SizedBox.shrink();
}
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return GestureDetector(
onVerticalDragUpdate: onVerticalDragUpdate,
child: Container(
width: double.infinity,
color: colorScheme.onSurface,
child: Align(
alignment: Alignment.topCenter,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8.0),
width: 32.0,
height: 4.0,
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8.0),
),
),
),
),
);
}
}
시트 핸들을 드래그 가능하게 만들기 위해 위젯을 하나 만들어줬다.
final ValueChanged<DragUpdateDetails> onVerticalDragUpdate;
또한 수직 드래그 시 콜백 될 것도 하나 만들어준 모습이다.
이렇게 만든 변수를 바로 넣어주게 된다.
return GestureDetector(
onVerticalDragUpdate: onVerticalDragUpdate,
이렇게 설정해놓으면 수직 드래그를 할 때마다 콜백이 실행되게 된다.
자 이제 바로 UI 를 만들어주면 되는데
Grabber(
onVerticalDragUpdate: (DragUpdateDetails details) {
setState(() {
_sheetPosition -= details.delta.dy / _dragSensitivity;
if (_sheetPosition < 0.25) { // 최소값 제한
_sheetPosition = 0.25;
}
if (_sheetPosition > 1.0) { // 최대값 제한
_sheetPosition = 1.0;
}
});
},
isOnDesktopAndWeb: _isOnDesktopAndWeb,
),
이렇게 직접 제작한 Grabber 위젯을 가져와서 y 방향 드래그 변화량을 시트 비율에 반영해 주게 된다. 이제 이러면 드래그를 한 만큼 크기가 변동되게 되는 것이다.
마지막으로 Flexible 을 사용하여 남는 공간을 다 차지할 수 있도록 만들었다.
Flexible(
child: ListView.builder(
controller: _isOnDesktopAndWeb ? null : scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Item $index', style: TextStyle(color: colorScheme.surface)),
);
},
),
),
이러면 크기가 변동되어도 알아서 채워지기 때문에 쉽게 작성할 수 있게 된다.
controller 에는 데스크톱/웹이면 외부에서 스크롤 제어할 수 있도록 선언해 놨다.
암튼 이렇게 DraggableScrollableSheet 위젯에 대해서 알아보았다. 이 위젯 자체는 막 그리 특색 있는 위젯은 아니지만 주는 기본적인 builder 함수를 가지고 있다는 점과 하위 속성들이 값을 조절하는 데에 있어서 꽤 좋아서 가끔씩은 사용되는 위젯이 아닐까 싶다. 도움이 되었길 바라며 마치겠다.