Last active
May 4, 2022 18:05
-
-
Save caseycrogers/91f7a953a3298e8479f2393f0319300d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
void main() => runApp(const MaterialApp(home: _DraggableSheetTest())); | |
class _DraggableSheetTest extends StatefulWidget { | |
const _DraggableSheetTest({Key? key}) : super(key: key); | |
@override | |
_DraggableSheetTestState createState() => _DraggableSheetTestState(); | |
} | |
class _DraggableSheetTestState extends State<_DraggableSheetTest> { | |
final TextEditingController _minSizeController = | |
TextEditingController(text: '.25'); | |
final TextEditingController _snapSizesController = | |
TextEditingController(text: '.5, .75'); | |
final TextEditingController _maxSizeController = | |
TextEditingController(text: '1'); | |
final TextEditingController _initialSizeController = | |
TextEditingController(text: '.6'); | |
final TextEditingController _animationDurationController = | |
TextEditingController(text: '0'); | |
final FocusNode _node = FocusNode(); | |
final DraggableScrollableController _sheetController = | |
DraggableScrollableController(); | |
bool _snapSizesIsValid = true; | |
bool _minIsValid = true; | |
bool _maxIsValid = true; | |
bool _initialIsValid = true; | |
bool _snap = true; | |
ScrollController? _contentController; | |
double _initialChildSize = .6; | |
List<double> _snapSizes = [.25, .5, .75, 1]; | |
int _jumpToSpeed = 0; | |
bool _speedIsValid = true; | |
final List<double> _goToSizes = [.4, .5, .6, .9]; | |
@override | |
void dispose() { | |
_node.dispose(); | |
super.dispose(); | |
} | |
Color _getColor(int index) { | |
if (index == _snapSizes.length - 1) { | |
return Colors.black; | |
} | |
return index % 2 == 0 ? Colors.lightBlue : Colors.white; | |
} | |
@override | |
Widget build(BuildContext context) { | |
Size size = MediaQuery.of(context).size; | |
return Stack( | |
children: [ | |
Column( | |
children: _snapSizes | |
.asMap() | |
.keys | |
.map((index) { | |
double nextSnapSize = | |
index == _snapSizes.length - 1 ? 1 : _snapSizes[index + 1]; | |
return Container( | |
color: _getColor(index), | |
height: size.height * (nextSnapSize - _snapSizes[index]), | |
); | |
}) | |
.toList() | |
.reversed | |
.toList(), | |
), | |
Positioned( | |
child: Container(color: Colors.red), | |
height: 1, | |
bottom: (size.height * _initialChildSize).ceil().toDouble(), | |
width: size.width, | |
), | |
DraggableScrollableSheet( | |
controller: _sheetController, | |
snap: _snap, | |
minChildSize: _snapSizes.first, | |
maxChildSize: _snapSizes.last, | |
initialChildSize: _initialChildSize, | |
snapSizes: _snapSizes, | |
builder: (context, scrollController) { | |
_contentController = scrollController; | |
return Material( | |
color: Colors.grey.withOpacity(.9), | |
child: ListView( | |
controller: scrollController, | |
children: List.generate(200, (index) => Text(index.toString())), | |
), | |
); | |
}, | |
), | |
Positioned( | |
bottom: MediaQuery.of(context).viewInsets.bottom, | |
width: MediaQuery.of(context).size.width, | |
child: Material( | |
color: Colors.white, | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: [ | |
Row( | |
children: [ | |
const Text(' Go to:'), | |
..._goToSizes.map((targetSize) { | |
return TextButton( | |
onPressed: () async { | |
if (_jumpToSpeed == 0) { | |
_contentController!.jumpTo(0); | |
_sheetController.jumpTo(targetSize); | |
} else { | |
if (targetSize < _sheetController.size && | |
_contentController!.offset != 0) { | |
final double pixelsPerMs = | |
(_sheetController.sizeToPixels(_sheetController.size) - | |
_sheetController | |
.sizeToPixels(targetSize)) / | |
_jumpToSpeed; | |
// The content should animate at the same speed as | |
// the sheet. | |
final int contentAnimationDuration = | |
(_contentController!.offset / pixelsPerMs) | |
.round(); | |
await _contentController!.animateTo( | |
0, | |
duration: Duration( | |
milliseconds: contentAnimationDuration, | |
), | |
curve: Curves.linear, | |
); | |
} | |
_sheetController.animateTo( | |
targetSize, | |
duration: Duration(milliseconds: _jumpToSpeed), | |
curve: Curves.linear, | |
); | |
} | |
}, | |
child: Text(targetSize.toString()), | |
); | |
}), | |
const Text('ms: '), | |
Expanded( | |
child: TextField( | |
controller: _animationDurationController, | |
decoration: InputDecoration( | |
errorText: _speedIsValid ? null : '', | |
), | |
onChanged: (newString) { | |
final int? newSpeed = int.tryParse(newString); | |
if (newSpeed != null) { | |
_jumpToSpeed = newSpeed; | |
_speedIsValid = true; | |
return; | |
} | |
_speedIsValid = false; | |
}, | |
), | |
), | |
], | |
), | |
Row( | |
children: [ | |
const Text(' Snap:'), | |
Switch( | |
value: _snap, | |
onChanged: (_) => setState(() { | |
_snap = !_snap; | |
}), | |
), | |
const Text(' Snap Sizes:'), | |
Flexible( | |
flex: 3, | |
child: TextField( | |
controller: _snapSizesController, | |
decoration: InputDecoration( | |
errorText: _snapSizesIsValid ? null : '', | |
), | |
onChanged: (newString) { | |
if (newString | |
.replaceAll(' ', '') | |
.split(',') | |
.every((v) => double.tryParse(v) != null)) { | |
_snapSizes = [ | |
_snapSizes.first, | |
...newString | |
.replaceAll(' ', '') | |
.split(',') | |
.map((v) => double.parse(v)), | |
_snapSizes.last, | |
]; | |
_snapSizesIsValid = true; | |
return; | |
} | |
_snapSizesIsValid = false; | |
}, | |
), | |
), | |
], | |
), | |
Row( | |
children: [ | |
const Text(' minSize:'), | |
Flexible( | |
flex: 1, | |
child: TextField( | |
controller: _minSizeController, | |
decoration: InputDecoration( | |
errorText: _minIsValid ? null : '', | |
), | |
onChanged: (newString) { | |
if (double.tryParse(newString) != null) { | |
_snapSizes.first = double.parse(newString); | |
_minIsValid = true; | |
return; | |
} | |
_minIsValid = false; | |
}, | |
), | |
), | |
const Text(' maxSize:'), | |
Flexible( | |
flex: 1, | |
child: TextField( | |
controller: _maxSizeController, | |
decoration: InputDecoration( | |
errorText: _maxIsValid ? null : '', | |
), | |
onChanged: (newString) { | |
if (double.tryParse(newString) != null) { | |
_snapSizes.last = double.parse(newString); | |
_maxIsValid = true; | |
return; | |
} | |
_maxIsValid = false; | |
}, | |
), | |
), | |
const Text(' initialSize:'), | |
Flexible( | |
flex: 1, | |
child: TextField( | |
controller: _initialSizeController, | |
decoration: InputDecoration( | |
errorText: _initialIsValid ? null : '', | |
), | |
onChanged: (newString) { | |
if (double.tryParse(newString) != null) { | |
_initialChildSize = double.parse(newString); | |
_initialIsValid = true; | |
return; | |
} | |
_initialIsValid = false; | |
}, | |
), | |
), | |
IconButton( | |
icon: const Icon(Icons.refresh), | |
onPressed: () { | |
FocusScope.of(context).requestFocus(_node); | |
_sheetController.reset(); | |
}, | |
), | |
IconButton( | |
icon: const Icon(Icons.check), | |
onPressed: () { | |
FocusScope.of(context).requestFocus(_node); | |
setState(() {}); | |
}, | |
), | |
], | |
), | |
], | |
), | |
), | |
), | |
], | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment