Problem
So basically it's quite an old problem, which I couldn't fix with Google. The problem is that DraggableScrollableSheet
doesn't size its maximum size based on its content size, but with only a static value maxChildSize
, which is just not good if you don't want to look at a lot of empty spaces in your sheet.
Any Solution?
Does anyone know some hack to set DragabbleScrollableSheet maxChildSize
based on its content size or give any alternatives to solve this issue?
Test project
I created a small application just for demonstrating the issue.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (_) => DraggableScrollableSheet(
initialChildSize: 0.8,
minChildSize: 0.3,
maxChildSize: 0.8,
expand: false,
builder: (_, controller) =>
ListView(
shrinkWrap: true,
controller: controller,
children: <Widget>[
Container(
color: Colors.red,
height: 125.0
),
Container(
color: Colors.white,
height: 125.0
),
Container(
color: Colors.green,
height: 125.0
),
],
)
)
),
child: const Text("Show scrollable sheet"),
),
],
),
),
);
}
}
- I tried with GlobalKey to get the size of Listview, which I don't think is possible, because it's just buggy and slow.
- I also tried LayoutBuilder, but with no serious results.
CodePudding user response:
I realized the problem is that you cannot measure the ListView size correctly in DraggableScrollableSheet, so I came up with an idea, that maybe I should measure the ListView somewhere else.
This is what I've come up with, it's quite inefficient, but it's better than nothing for now. I would also like to know a better solution though.
class _MyHomePageState extends State<MyHomePage> {
final _key = GlobalKey();
Size? _size;
@override
void initState() {
calculateSize();
super.initState();
}
void calculateSize() =>
WidgetsBinding.instance?.addPersistentFrameCallback((_) {
_size = _key.currentContext?.size;
});
double getTheRightSize(double screenHeight) {
final maxHeight = 0.8 * screenHeight;
final calculatedHeight = _size?.height ?? maxHeight;
return calculatedHeight > maxHeight ? maxHeight / screenHeight : calculatedHeight / screenHeight;
}
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Scaffold(
body: Stack(
children: [
Opacity(
key: _key,
opacity: 0.0,
child: MeasuredWidget(),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (_) => DraggableScrollableSheet(
initialChildSize: getTheRightSize(height),
minChildSize: 0.3,
maxChildSize: getTheRightSize(height),
expand: false,
builder: (_, controller) =>
MeasuredWidget(controller: controller,)
)
),
child: const Text("Show scrollable sheet"),
),
],
),
),
],
),
);
}
}
class MeasuredWidget extends StatelessWidget {
ScrollController? controller;
MeasuredWidget({ Key? key, this.controller }) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: ListView(
shrinkWrap: true,
controller: controller,
children: <Widget>[
Container(
color: Colors.red,
height: 400.0
),
Container(
color: Colors.white,
height: 400.0
),
Container(
color: Colors.green,
height: 200.0
),
],
),
);
}
}
CodePudding user response:
The answer is why measuring ListView size didn't work in DraggabbleScrollableSheet is that ListView only render content which is on the screen, for this reason you have to use SingleChildScrollView in this case.
So the code eventually look like this:
class _MyHomePageState extends State<MyHomePage> {
final _key = GlobalKey();
Size? _size;
@override
void initState() {
calculateSize();
super.initState();
}
void calculateSize() =>
WidgetsBinding.instance?.addPersistentFrameCallback((_) {
_size = _key.currentContext?.size;
});
double getTheRightSize(double screenHeight) {
final maxHeight = 0.8 * screenHeight;
final calculatedHeight = _size?.height ?? maxHeight;
return calculatedHeight > maxHeight ? maxHeight / screenHeight : calculatedHeight / screenHeight;
}
@override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (_) => DraggableScrollableSheet(
initialChildSize: getTheRightSize(height),
minChildSize: getTheRightSize(height) - 0.2 > 0 ? getTheRightSize(height) - 0.2 : 0.3,
maxChildSize: getTheRightSize(height),
expand: false,
builder: (_, controller) =>
Container(
key: _key,
child: SingleChildScrollView(
controller: controller,
child: Column(
children: [
Container(
color: Colors.red,
height: 200.0
),
Container(
color: Colors.white,
height: 200.0
),
Container(
color: Colors.green,
height: 200.0
),
],
),
),
)
)
),
child: const Text("Show scrollable sheet"),
),
],
),
),
);
}
}