DraggableScrollableSheet closes when swiping up - flutter

I have noticed if maxChildSize and minChildSize has the same value then swiping up on the contents of DraggableScrollableSheet closes the bottom sheet.
The expected behaviour is to scroll down the ListView provided in the builder argument of the DraggableScrollableSheet.
I believe the expected behaviour should be followed even when both maxChildSize and minChildSize are the same.
Am I missing something?
Here is the minimum reproducible code:
import 'package:flutter/material.dart';
class DraggableBottomSheetTest extends StatelessWidget {
const DraggableBottomSheetTest({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) {
return DraggableScrollableSheet(
maxChildSize: 0.5,
minChildSize: 0.5,
initialChildSize: 0.5,
expand: false,
builder: (context, controller) => ListView.builder(
controller: controller,
itemCount: 50,
itemBuilder: (context, index) => ListTile(
title: Text('Item $index'),
),
),
);
},
);
},
child: Text('Show Modal BottomSheet'),
),
),
);
}
}

import 'package:flutter/material.dart';
class DraggableBottomSheetTest extends StatelessWidget {
const DraggableBottomSheetTest({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true, // set this to true
builder: (context) {
return DraggableScrollableSheet(
maxChildSize: 0.5,
minChildSize: 0.5,
initialChildSize: 0.5,
expand: false,
builder: (context, controller) => ListView.builder(
controller: controller,
itemCount: 50,
itemBuilder: (context, index) => ListTile(
title: Text('Item $index'),
),
),
);
},
);
},
child: Text('Show Modal BottomSheet'),
),
),
);
}
}

Related

Flutter transition like iOS 13 modal full screen

I would like to have the iOS-Modal-Transition where the new screen animates from the bottom and the old screen is being pushed behind. I found this very promising package:
modal_bottom_sheet
This is the function I am using to show the modal:
showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context) => Container(
color: AppColors.blue,
),
);
However this is not working a 100% correctly as the view behind is not being pushed in the back.
What am I missing here? Let me know if anything is unclear!
Here is some more of my code:
This is my whole page, from where I would like to have the transition:
class _MonthPageState extends State<MonthPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.secondary,
body: SafeArea(
child: Stack(
children: [
...
Positioned(
bottom: 10,
right: 20,
child: Hero(
tag: widget.month.name + 'icon',
child: AddButton(
onTapped: () {
showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context) => Container(
color: AppColors.blue,
),
);
},
),
),
),
],
),
),
);
}
And this is my Router:
class AppRouter {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialWithModalsPageRoute(
builder: (context) => HomePage(),
);
case '/month':
final Month month = settings.arguments as Month;
return _buildTransitionToMonthPage(month);
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
),
);
}
}
static PageRouteBuilder _buildTransitionToMonthPage(Month month) {
return PageRouteBuilder(
transitionDuration: Duration(milliseconds: 450),
reverseTransitionDuration: Duration(milliseconds: 450),
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return MonthPage(
month: month,
);
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return FadeTransition(opacity: animation, child: child);
},
);
}
}
In order to get that pushing behind animation, you need to use CupertinoScaffold alongside with CupertinoPageScaffold, e.g.
#override
Widget build(BuildContext context) {
return CupertinoScaffold(
transitionBackgroundColor: Colors.white,
body: Builder(
builder: (context) => CupertinoPageScaffold(
backgroundColor: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: ElevatedButton(
child: Text('show modal'),
onPressed: () =>
CupertinoScaffold.showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.white,
builder: (context) => Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () => Navigator.of(context)
.popUntil((route) =>
route.settings.name == '/'),
child: Text('return home'),
),
)),
)),
),
],
),
),
),
);
}

Responsive GridView fullScreen flutter

I would like to have my gridview to take the whole screen, but i can't find a way to do this.
I already check multiple blog like :
https://medium.com/nusanet/flutter-gridview-bad48c1f216c
But this doesn't seem to work in my case.
Even when i'm using MediaQuery Data to get the screen width and height to adapt the childAspectRation.
Here my gridview :
GridView.count(
childAspectRatio: (widthScreen / heightScreen),
shrinkWrap: true,
crossAxisCount: 2,
physics: NeverScrollableScrollPhysics(),
crossAxisSpacing: 15,
mainAxisSpacing: 10,
padding: const EdgeInsets.all(5),
children: <Widget>[
CardItem(
imageUrl: 'assets/images/apprendreajouer.jpg',
title: 'Apprendre à jouer',
function: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => ListLevelsPage(
pageTitle: "Cours",
pageKey: 1,
context: context,
),
),
);
},
),
CardItem(
imageUrl: 'assets/images/exercices.jpeg',
title: 'Exercice',
function: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => ListLevelsPage(
pageTitle: "Exercices",
pageKey: 2,
context: context,
),
),
);
},
),
CardItem(
imageUrl: 'assets/images/partition.jpg',
title: 'Partition',
function: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => ListLevelsPage(
pageTitle: "Partition",
pageKey: 3,
context: context,
),
),
);
},
),
CardItem(
imageUrl: 'assets/images/dictionnaire.jpeg',
title: 'Mon dictionnaire',
function: () {
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => ListLevelsPage(
pageTitle: "Dictionnaire d'accords",
pageKey: 4,
context: context,
),
),
);
},
),
CardItem(
imageUrl: 'assets/images/examen.jpeg',
title: 'Examen',
),
CardItem(
imageUrl: 'assets/images/apropos.jpg',
title: 'A propos',
),
],
),
Here below an image of what i want :
Update of what i get using #Thierry source code :
Any help would be great.
You have some discrepancies in your code.
You cannot define:
6 children for your GridView
childAspectRatio of widthScreen / heightScreen
crossAxisCount: 2
You can keep your childAspectRatio and crossAxisCount if you reduce the number of children to 4.
If you want 6 children on 3 Rows, then change the childAspectRatio to MediaQuery.of(context).size.aspectRatio * 3 / 2.
If you want 6 children on 2 Rows, then increase crossAxisCount to 3, and change the childAspectRatio to MediaQuery.of(context).size.aspectRatio * 2 / 3.
Note: You can continue like that. if you want m Rows of n children, the childAspectRatio should be m/n.
Full source code (6 children on 3 Rows)
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('GridView Demo')),
body: LayoutBuilder(
builder: (context, constraints) => GridView.count(
childAspectRatio: constraints.biggest.aspectRatio * 3 / 2,
shrinkWrap: true,
crossAxisCount: 2,
physics: NeverScrollableScrollPhysics(),
children: List.generate(
6,
(index) => Padding(
padding: const EdgeInsets.all(4.0),
child: CardItem(
imageUrl: 'assets/images/abstract$index.jpg',
title: 'Abstract $index',
),
),
).toList(),
),
),
);
}
}
class CardItem extends StatelessWidget {
final String imageUrl;
final String title;
final VoidCallback function;
const CardItem({Key key, this.imageUrl, this.title, this.function})
: super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: function,
child: Card(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(imageUrl),
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
child: Text(title),
),
),
);
}
}

barrierDismissible in showGeneralDialog is not working with Scaffold

I am still new with Flutter. I try to make my dialog to be able to dismiss when click outside of the dialog. However if i use Scaffold, the barrierDismissible:true is not working. I tried to use Wrap but an error : No Material widget found will be displayed. Is there any idea on how to dismiss the dialog?
This is my code:
showGeneralDialog(
barrierDismissible: true,
pageBuilder: (context, anim1, anim2) {
context1 = context;
return StatefulBuilder(
builder: (context, setState) {
return Scaffold(
backgroundColor: Colors.black .withOpacity(0.0),
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
child: InkWell()
)
)
}
}
)
Scaffold is not required to display showGeneralDialog. The Material widget was required in your code because the InkWell widget needs a Material ancestor. You could use any widget that provides material such as Card or Material widget itself. Also barrierLabel cannot be null.
Please see the working code below or you can directly run the code on Dartpad https://dartpad.dev/6c047a6cabec9bbd00a048c972098671
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text("showGeneralDialog Demo"),
),
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel:
MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black54,
pageBuilder: (context, anim1, anim2) {
return Center(
child: Container(
width: 200,
height: 100,
child: StatefulBuilder(
builder: (context, snapshot) {
return const Card(
color: Colors.white,
child: Center(
child: InkWell(
child: Text(
"Press outside to close",
style: TextStyle(color: Colors.black),
),
),
),
);
},
),
),
);
},
);
},
child: const Text("Show Dialog"));
}
}
For anyone who needs to use a Scaffold in their AlertDialogs (perhaps to use ScaffoldMessenger), here is the simple work around:
Wrap the Scaffold with an IgnorePointer. The "barrierDismissible" value will now work.
#override
Widget build(BuildContext context) {
return IgnorePointer(
child: Scaffold(
backgroundColor: Colors.transparent,
body: AlertDialog(
title: title,
content: SizedBox(
width: MediaQuery.of(context).size.width,
child: SingleChildScrollView(
child: ListBody(
children: content
),
),
),
actions: actions,
insetPadding: const EdgeInsets.all(24.0),
shape: Theme.of(context).dialogTheme.shape,
backgroundColor: Theme.of(context).dialogTheme.backgroundColor,
)
),
);
}
Add this in showGeneralDialog
barrierLabel: ""
Code will look like this
showGeneralDialog(
barrierDismissible: true,
barrierLabel: "",
pageBuilder: (context, anim1, anim2) {
context1 = context;
return StatefulBuilder(
builder: (context, setState) {
return Scaffold(
backgroundColor: Colors.black .withOpacity(0.0),
body: Align(
alignment: Alignment.bottomCenter,
child: Container(
child: InkWell()
)
)
}
}
)
I was encountering this issue when using showGeneralDialog on top of a map view. The root cause of my issue was that I had wrapped the dialog in a PointerInterceptor.
To fix it, I had to set intercepting to false.
PointerInterceptor(
intercepting: onMap, // false when not an map
child: Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
)
)

Is it possible to drag DraggableScrollableSheet programmatically in flutter?

I want to know how to drag the DraggableScrollableSheet widget to go up or down programmatically. For example, based on a timer class.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('DraggableScrollableSheet'),
),
body: SizedBox.expand(
child: 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'));
},
),
);
},
),
),
);
}
}

How do you know if DraggableScrollableSheet is collapsed or expanded

In my app there is a DraggableScrollableSheet and a FAB. I want the FAB be invisible if the DraggableScrollableSheet is expanded. I need to check the event of expansion
I tried to attach a listener to the scrollController and check the value of scrollController.offset. But I realized that the listener is triggered just when the DraggableScrollableSheet is completly expanded and not before. Is there another way to check if it is expanded or collapsed?
double appbarSize = 0.08;
double offsetVisibility=100.0;
bool FAB_visibility=true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Visibility(
visible: FAB_visibility,
child: FloatingActionButton(
child: Icon(Icons.add),
),
),
body: SizedBox.expand(
child: DraggableScrollableSheet(
maxChildSize: 0.8,
minChildSize: appbarSize,
initialChildSize: appbarSize,
builder: (BuildContext context, ScrollController scrollController) {
_scrollListener() {
if(FAB_visibility==false && scrollController.offset<=offsetVisibility){
setState(() {
FAB_visibility=true;
});
}
else if(FAB_visibility==true && scrollController.offset>offsetVisibility){
setState(() {
FAB_visibility=false;
});
}
}
scrollController.addListener(_scrollListener);
return Container(
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(10.0),
topRight: const Radius.circular(10.0))),
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
)),
);
}
Thanks to flutter team I understood how to do it!
We should wrap DraggableScrollableSheet in a NotificationListener
This is the working code:
class _MyHomePageState extends State<MyHomePage> {
double appbarSize = 0.08;
double offsetVisibility = 100.0;
bool FAB_visibility = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox.expand(
child: NotificationListener<DraggableScrollableNotification>(
onNotification: (DraggableScrollableNotification DSNotification){
if(FAB_visibility && DSNotification.extent>=0.2){
setState(() {
FAB_visibility=false;
});
}
else if(!FAB_visibility && DSNotification.extent<0.2){
setState(() {
FAB_visibility=true;
});
}
},
child: DraggableScrollableSheet(
maxChildSize: 0.8,
minChildSize: appbarSize,
initialChildSize: appbarSize,
builder: (BuildContext context, ScrollController scrollController) {
return Container(
child: ListView.builder(
controller: scrollController,
itemCount: 25,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('Item $index'));
},
),
);
},
),
)),
floatingActionButton: Visibility(
visible: FAB_visibility,
child: FloatingActionButton(
child: Icon(Icons.add),
),
),
);
}
}