I'm wondering whether it's possible to create a draggable sheet that stretches from both the top and bottom. When I pull the sheet from the bottom to roughly 75% of its complete height, another sheet will start extending from the top until both sheets contact.
DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.blue[100],
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
I observed that the scroll controller contains the ScrollPosition property, which I believe may be used to initiate a top to bottom draggable sheet. My current plan is to construct a listener, add it to the scroll controller using the scroll controller class's notifyListeners function, and then put another draggable sheet to the top of the stack whenever the conditions are satisfied.
Edit 1: This can be done easily, you only need 3 widgets, animatedContainer, gestureDetector and transform.
yeah its very possible, works exactly like the bolt app cheers
import 'package:flutter/material.dart';
class AnimationSheet extends StatefulWidget {
const AnimationSheet({Key? key}) : super(key: key);
@override
State<AnimationSheet> createState() => _AnimationSheetState();
}
class _AnimationSheetState extends State<AnimationSheet> {
double _percent = 0.0;
bool isDragged = false;
double initialHeight = 0.0;
checkStateDragged() {
if (_percent > 0.5) {
setState(() {
isDragged = true;
});
} else {
setState(() {
isDragged = false;
});
}
}
@override
Widget build(BuildContext context) {
// handle hiding disabled search field
checkStateDragged();
return Scaffold(
drawer: Drawer(
elevation: 0,
child: SafeArea(
child: Column(
children: const [
Text("Hello world"),
],
),
),
),
body: SafeArea(
child: Stack(
children: [
Positioned.fill(
bottom: MediaQuery.of(context).size.height * 0.2,
child: Image.asset(
'assets/images/background.jpg',
fit: BoxFit.cover,
),
),
Positioned(
top: 10.0,
left: 10.0,
child: FloatingActionButton(
backgroundColor: Colors.white,
onPressed: () {},
child: const Icon(
Icons.menu,
color: Colors.black,
),
),
),
/* draggable scrollable sheet*/
Positioned.fill(
child: NotificationListener<DraggableScrollableNotification>(
onNotification: (notification) {
setState(() {
_percent = 2 * notification.extent - 0.8;
});
return true;
},
child: DraggableScrollableSheet(
maxChildSize: 0.9,
minChildSize: 0.4,
builder: (BuildContext context, ScrollController scrollController) {
return Material(
elevation: 10.0,
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20.0),
),
color: Colors.white,
child: Padding(
padding: const EdgeInsets.only(
left: 10.0,
right: 10.0,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15.0),
Container(
height: 10.0,
margin: const EdgeInsets.symmetric(horizontal: 120.0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(50.0),
),
),
const SizedBox(height: 15.0),
Container(
margin: const EdgeInsets.symmetric(horizontal: 5.0),
child: const Text(
"Akwaaba !",
style: TextStyle(
color: Colors.black54,
),
),
),
Container(
margin: const EdgeInsets.symmetric(
horizontal: 5.0,
vertical: 5.0,
),
decoration: const BoxDecoration(
color: Colors.white,
),
child: const Text(
"Where are you going?",
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w900,
fontSize: 22.0,
),
),
),
!isDragged
? GestureDetector(
onTap: () {
setState(() {
_percent = 1.0;
});
},
child: TextFormField(
decoration: InputDecoration(
enabled: false,
hintText: "Search Destination",
filled: true,
fillColor: Colors.white,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(15.0),
),
gapPadding: 2.0,
),
prefixIcon: Icon(
Icons.search,
color: Colors.purple[300],
),
),
),
)
: Container(),
Expanded(
child: ListView.builder(
controller: scrollController,
padding: const EdgeInsets.only(bottom: 40.0),
itemCount: 20,
itemBuilder: (context, index) {
return const ListTile(
contentPadding: EdgeInsets.zero,
leading: Icon(
Icons.location_on,
color: Colors.black,
),
title: Text(
"Street No 12345 NY Street",
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.w700,
),
),
subtitle: Text(
"New York City",
style: TextStyle(
color: Colors.black54,
),
),
);
},
),
),
],
),
),
);
},
),
),
),
/* search destination */
Positioned(
left: 0.0,
right: 0.0,
top: -180 * (1 - _percent),
child: Opacity(
opacity: _percent,
child: const SearchDestination(),
),
),
/* select destination on map */
Positioned(
left: 0.0,
right: 0.0,
bottom: -50 * (1 - _percent),
child: Opacity(
opacity: _percent,
child: const PickOnMap(),
),
),
],
),
),
);
}
}
// search destination sheet
class SearchDestination extends StatelessWidget {
const SearchDestination({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
elevation: 2.0,
child: Padding(
padding: const EdgeInsets.only(
left: 10.0,
right: 10.0,
top: 10.0,
bottom: 10.0,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Icon(
Icons.arrow_back_ios,
color: Colors.black87,
),
Expanded(
child: Center(
child: Text(
"Choose Destination".toUpperCase(),
style: const TextStyle(
color: Colors.black87,
fontWeight: FontWeight.w900,
fontSize: 22.0,
),
),
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: Column(
children: [
const SizedBox(height: 10.0),
// pick up location
TextFormField(
decoration: InputDecoration(
hintText: "Avenue 34 St 34 NY",
filled: true,
fillColor: Colors.grey[200],
border: InputBorder.none,
),
),
const SizedBox(height: 10.0),
// destination
TextFormField(
decoration: InputDecoration(
hintText: "Where are you going",
filled: true,
fillColor: Colors.grey[200],
border: InputBorder.none,
),
),
const SizedBox(height: 10.0),
],
),
),
],
),
),
);
}
}
/// select destination on map
class PickOnMap extends StatelessWidget {
const PickOnMap({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
elevation: 5.0,
color: Colors.amber,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20.0,
vertical: 10.0,
),
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(
Icons.place_sharp,
color: Colors.purple,
),
SizedBox(width: 30.0),
Text(
"Select on Map",
style: TextStyle(
color: Colors.black87,
fontSize: 22.0,
fontWeight: FontWeight.w700,
),
),
],
),
),
),
);
}
}