snap_bottom_sheet 0.1.0
snap_bottom_sheet: ^0.1.0 copied to clipboard
A customizable bottom sheet with snap positions for Flutter, inspired by iOS-style sheet detents.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:snap_bottom_sheet/snap_bottom_sheet.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SnapBottomSheet 예제',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: const SnapSheetDemo(),
);
}
}
class SnapSheetDemo extends StatefulWidget {
const SnapSheetDemo({super.key});
@override
State<SnapSheetDemo> createState() => _SnapSheetDemoState();
}
class _SnapSheetDemoState extends State<SnapSheetDemo> {
// 시트 컨트롤러 생성
final _controller = SnapSheetController();
SnapPosition _currentPosition = SnapPosition.medium;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SnapBottomSheetScaffold(
// 앱바 설정
appBar: AppBar(
title: const Text('SnapBottomSheet 예제'),
actions: [
IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () {
// 단계적으로 시트 높이 증가
if (_currentPosition == SnapPosition.minimum) {
_controller.snapToMedium();
} else if (_currentPosition == SnapPosition.medium) {
_controller.snapToMaximum();
}
},
),
IconButton(
icon: const Icon(Icons.arrow_downward),
onPressed: () {
// 단계적으로 시트 높이 감소
if (_currentPosition == SnapPosition.maximum) {
_controller.snapToMedium();
} else if (_currentPosition == SnapPosition.medium) {
_controller.snapToMinimum();
}
},
),
],
),
// 메인 화면 콘텐츠
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Colors.blue, Colors.lightBlueAccent],
),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'SnapBottomSheet 라이브러리 데모',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
Text(
'현재 시트 위치: $_currentPosition',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
const SizedBox(height: 20),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blue,
),
onPressed: () {
_controller.snapToMaximum();
},
child: const Text('시트 최대화'),
),
],
),
),
),
// 시트 설정
controller: _controller,
minHeightRatio: 0.1,
mediumHeightRatio: 0.5,
maxHeightRatio: 0.9,
sheetBackgroundColor: Colors.white,
borderRadius: 24.0,
showDragHandle: true,
animationCurve: const SpringCurve(
mass: 1.0,
stiffness: 200.0,
damping: 25.0,
),
// 시트 위치 변경 콜백
onPositionChanged: (position) {
setState(() {
_currentPosition = position;
});
},
// 시트 내부 콘텐츠
sheetContent: ListView(
padding: const EdgeInsets.all(16.0),
children: [
const Text(
'SnapBottomSheet',
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
const Text('이 시트는 세 가지 위치로 스냅됩니다:', style: TextStyle(fontSize: 16)),
const SizedBox(height: 8),
ListTile(
leading: const Icon(Icons.arrow_downward),
title: const Text('최소 높이 (화면의 10%)'),
tileColor:
_currentPosition == SnapPosition.minimum
? Colors.blue.withValues(alpha: 0.1)
: null,
),
ListTile(
leading: const Icon(Icons.drag_handle),
title: const Text('중간 높이 (화면의 50%)'),
tileColor:
_currentPosition == SnapPosition.medium
? Colors.blue.withValues(alpha: 0.1)
: null,
),
ListTile(
leading: const Icon(Icons.arrow_upward),
title: const Text('최대 높이 (화면의 90%)'),
tileColor:
_currentPosition == SnapPosition.maximum
? Colors.blue.withValues(alpha: 0.1)
: null,
),
const SizedBox(height: 16),
const Text(
'사용 방법:',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text('• 시트를 위아래로 드래그하여 위치 변경'),
const Text('• 빠르게 쓸어올리거나 내려서 다음 위치로 이동'),
const Text('• 앱바의 화살표 버튼으로 위치 제어'),
// 스크롤 테스트를 위한 추가 콘텐츠
const SizedBox(height: 40),
...List.generate(
15,
(index) => ListTile(
title: Text('항목 ${index + 1}'),
subtitle: Text('스크롤 테스트 항목'),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('항목 ${index + 1} 선택됨'),
duration: const Duration(seconds: 1),
),
);
},
),
),
],
),
);
}
}