Home > Mobile >  How to set DraggableScrollableSheet maximum size dynamically, according to its content?
How to set DraggableScrollableSheet maximum size dynamically, according to its content?

Time:03-28

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"),
            ),
          ],
        ),
      ),
    );
  }
}
  • Related