Flutter로 웹 애플리케이션을 개발할 때, 기본적으로 제공되는 SingleChildScrollView는 마우스 드래그로 스크롤이 가능하지 않을 수 있습니다. 특히, kIsWeb을 사용하여 웹 환경에서 마우스 드래그와 터치를 동시에 지원하고자 할 때 추가적인 설정이 필요합니다. 이번 포스트에서는 Flutter 웹에서 드래그 스크롤 문제를 해결하기 위해 작성한 커스텀 위젯을 소개합니다.
기본적으로 Flutter의 SingleChildScrollView는 웹 환경에서 마우스 휠로는 스크롤을 지원하지만, 마우스를 클릭하고 드래그하여 스크롤하는 동작은 지원하지 않습니다. 이를 해결하기 위해 MaterialScrollBehavior와 PointerDeviceKind를 활용하거나, 완전히 커스텀 위젯을 만들어야 합니다.
스크롤 화면에서 vertical한 움직임은 scrollBehavior에 MaterialScrollBehavior를 추가하는 것만으로도 문제가 해결됩니다. 다만 웹 환경아닌경우에는 추가할 필요가 없어서 아래처럼 분기처리를 해서 사용하고 있습니다 .
getScrollBehavior() {
if (kIsWeb) {
return MaterialScrollBehavior().copyWith(
dragDevices: {
PointerDeviceKind.mouse,
PointerDeviceKind.touch,
PointerDeviceKind.stylus,
PointerDeviceKind.unknown
},
);
}
return null;
}
CustomScrollView(
scrollBehavior: getScrollBehavior(),
physics: const ClampingScrollPhysics(),
controller: controller.scrollController,
slivers: [
])
이 코드를 사용하면 마우스, 터치, 스타일러스 등을 활용한 드래그 동작이 가능해집니다.
버티컬에서는 SingleChildScrollView에 scrollBehavior가 추가가 되지 않아 아래 처럼 MouseRegion 위젯을 사용해서 드래그 기능을 구현했습니다.
class ResponsiveHorizontalScroll extends StatelessWidget {
final Widget Function(BuildContext) itemBuilder;
final double scrollSpeed;
final Color? hoverColor;
const ResponsiveHorizontalScroll({
Key? key,
required this.itemBuilder,
this.scrollSpeed = 1.5,
this.hoverColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (kIsWeb) {
return _buildWebScroll();
}
return _buildMobileScroll(context);
}
Widget _buildWebScroll() {
return EnhancedDraggableScroll(
itemBuilder: itemBuilder,
scrollSpeed: scrollSpeed,
hoverColor: hoverColor,
);
}
Widget _buildMobileScroll(context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
child: itemBuilder(context),
);
}
}
웹 환경에서 드래그를 활용한 스크롤 동작을 구현하기 위해 EnhancedDraggableScroll 위젯을 작성했습니다.
class EnhancedDraggableScroll extends StatefulWidget {
final Widget Function(BuildContext) itemBuilder;
final double scrollSpeed;
final Color? hoverColor;
const EnhancedDraggableScroll({
Key? key,
required this.itemBuilder,
this.scrollSpeed = 1.0,
this.hoverColor,
}) : super(key: key);
@override
State<EnhancedDraggableScroll> createState() => _EnhancedDraggableScrollState();
}
class _EnhancedDraggableScrollState extends State<EnhancedDraggableScroll> {
final ScrollController _scrollController = ScrollController();
bool _isDragging = false;
bool _isHovering = false;
double _startX = 0;
double _scrollStartPosition = 0;
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) => _handleHover(true),
onExit: (_) => _handleHover(false),
cursor: _isDragging ? SystemMouseCursors.grabbing : SystemMouseCursors.grab,
child: Listener(
onPointerDown: _handleDragStart,
onPointerMove: _handleDragUpdate,
onPointerUp: _handleDragEnd,
child: Container(
decoration: BoxDecoration(
color: _isHovering ? widget.hoverColor : null,
),
child: SingleChildScrollView(
controller: _scrollController,
scrollDirection: Axis.horizontal,
physics: const BouncingScrollPhysics(),
child: widget.itemBuilder(context),
),
),
),
);
}
void _handleDragStart(PointerDownEvent event) {
setState(() {
_isDragging = true;
_startX = event.position.dx;
_scrollStartPosition = _scrollController.position.pixels;
});
}
void _handleDragUpdate(PointerMoveEvent event) {
if (!_isDragging) return;
final double diff = (_startX - event.position.dx) * widget.scrollSpeed;
_scrollController.jumpTo(
(_scrollStartPosition + diff).clamp(
0.0,
_scrollController.position.maxScrollExtent,
),
);
}
void _handleDragEnd(PointerUpEvent event) {
setState(() {
_isDragging = false;
});
}
void _handleHover(bool isHovering) {
setState(() {
_isHovering = isHovering;
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
웹 환경:
EnhancedDraggableScroll을 통해 드래그 스크롤 동작을 지원.
마우스 휠과 드래그를 모두 활용 가능.
모바일 환경:
기본 SingleChildScrollView 동작으로 터치 기반 스크롤을 지원.
ResponsiveHorizontalScroll(
itemBuilder: (context) => Row(
children: [
for (int i = 0; i < 10; i++)
Container(
width: 100,
height: 100,
margin: const EdgeInsets.all(8),
color: Colors.blue,
child: Center(child: Text('Item \$i')),
),
],
),
scrollSpeed: 1.5,
hoverColor: Colors.grey.withOpacity(0.1),
)
Flutter 웹에서 드래그 스크롤 문제는 EnhancedDraggableScroll을 통해 효과적으로 해결할 수 있습니다. 이 코드는 웹 환경에서 직관적인 사용자 경험을 제공하며, 모바일 환경에서도 동일한 구성 요소를 재사용할 수 있습니다. 이 솔루션을 활용하여 더욱 유연하고 강력한 UI를 설계해보세요!