중구난방 사용하면 패키지들을 삭제하고 기본 위젯으로 교체하면서 부터 발생했던 문제다.
플러터의 기본 showModalBottomSheet 를 사용하면서부터 자식 위젯이
Scrollable 위젯(ListView, SingleChildScrollView 등등)인 경우 모달을 드래그로 닫을 수 없다는걸 알게 되었다.
플러터의 기본 모달바텀시트를 쓰기 전에는 modal_bottom_sheet 패키지를 사용했었는데,
해당 패키지를 사용하면 Scollable 위젯이 들어있어도 드래그로 닫을 수 있지만 버전이 올라갈 때나 마이그레이션 시,
에러가 발생한 경우가 있었고, 주기적으로 소스 수정이 필요할 수 있다.
그리하여 기본 위젯으로 변경하기로 했다.
핵심 이유! 기본 위젯으로 충분히 구현 가능한대도 무분별하게 패키지를 사용하고 싶지 않아서
패키지를 쓰지 않으면서 해당 문제를 해결하고 싶었다.
import 'package:flutter/material.dart';
class TestCloseModal extends StatefulWidget {
const TestCloseModal({Key? key}) : super(key: key);
@override
State<TestCloseModal> createState() => _TestCloseModalState();
}
class _TestCloseModalState extends State<TestCloseModal> {
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
showDragHandle: true,
isScrollControlled: true,
useSafeArea: true,
builder: (context) => Container(
height: height * 0.8,
width: double.infinity,
color: Colors.grey[50],
child: Center(child: Text("안녕")),
),
);
},
child: Text("드래그로 닫을 수 있는 경우"),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
showDragHandle: true,
isScrollControlled: true,
useSafeArea: true,
builder: (context) => Container(
height: height * 0.8,
color: Colors.grey[50],
child: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) => ListTile(
title: Text("ITEM $index"),
),
),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
),
child: Text("드래그로 닫을 수 없는 경우"),
),
],
),
),
);
}
}


자식 위젯의 스크롤링 때문에 모달을 스크롤 할 수 없게 되어 드래그로 닫을 수 없게 된다.
showDragHandle: true,
해당 속성을 true로 해주게 되면 모달 상단에 핸들러가 생겨 핸들러를 드래그해서 닫을 수 있지만 편리하진 않다.
이벤트 알림을 해주는 NotificationListener 위젯을 사용해서 완벽하진 않지만 기능을 구현했다.

NotificationListener 위젯에 ScollNotification 종속성을 주입해서 스크롤 이벤트를 알 수 있도록 한다.
notification.metrics 안에는 스크롤링 할 때의 많은 정보들을 알 수 있는데, pixels 값만 사용할 것이다.
pixels 값은 스크롤의 현재 높이라고 보면 된다. 스크롤이 최상단일 때 0.0 이 출력된다.

아이폰에서는 위젯의 마지막 위치보다 스크롤링이 더 많이 되어 pixels 값이 마이너스까지 가게 되는데 이 점을 이용했다.

physics 속성에 BouncingScollPhysics() 값을 넣어준다.
이유는 안드로이드에서도 아이폰 처럼 마이너스 값까지 스크롤링이 동일하게 되도록 필수로 넣어줘야 한다.
필자는 pixels 값이 -180 보다 작아질 때 창이 닫히도록 했다.
pixels 값은 직접 스크롤을 테스트 해보면서 이정도에서 닫히게 해야겠다고 생각하면서 정해주면 된다.

하지만 이렇게하면 문제가 발생한다.


스크롤링 이벤트 리스너가 이벤트 감지를 자주 하기 때문에 Navigator.pop(context); 메소드가 한 번만 실행되는것이 아닌
여러번이 실행되어 의도치 않게 여러 화면이 종료되어 에러가 발생한다.
그래서 Navigator.pop(context); 메소드가 딱 한 번만 실행되기 위한 bool 변수를 추가해 주었다.
bool _dragToClose = false;

변수의 기본값은 false 이며, pixels 값이 -180보다 작아졌을 때 먼저 변수에 true를 할당해 주고
Navigator.pop(context); 메소드를 실행시켜 준다. 변수가 false 일때만 닫을 수 있도록 했다.
참고로 onNotification 함수는 bool 값을 리턴해주어야 하는데, true / false 는 알림 버블링에 대한 값이고
true는 버블링 취소, false는 추가 조상에게 버블링을 계속 전달한다.
(이 내용은 onNotification 👈 해당 문서를 참조하면 된다.)
그리고 다시 테스트해보면 pixels 값이 -180보다 작은 경우를 여러번 감지해도
early return으로 함수를 빠져나가기 때문에 정상적으로 모달만 닫히게 된다.

패키지처럼 자연스럽게 모달이 드래그 되면서 점점 내려가다가 닫히게 하고 싶었지만
해당 방법은 찾지 못하여 조금 다른 방식으로 구현해 보았다.
패키지를 삭제하고 기본 위젯으로 변경한 후 조금 불편함을 느껴왔는데 이렇게라도 해결해서 나름 뿌듯했다...🥺
'FLUTTER' 카테고리의 다른 글
[FLUTTER] 위젯의 사이즈 구하기 (0) | 2025.02.17 |
---|---|
[FLUTTER] 스크롤 시 이미지 크기 상호작용하는 UI 만들기 (0) | 2025.01.28 |
[FLUTTER] TextInputAction.next 시 원치 않는 포커스 스킵 방법 (TextField의 suffixIcon) (0) | 2023.06.28 |
[FLUTTER] Widget - Container (0) | 2022.09.11 |