如何根据其内容动态设置 DraggableScrollableSheet 最大尺寸?

发布于 01-16 21:28 字数 2426 浏览 3 评论 0原文

问题

所以基本上这是一个相当老的问题,我无法通过谷歌解决。问题是 DraggableScrollableSheet 不会根据其内容大小来调整其最大大小,而只有一个静态值 maxChildSize,如果您不这样做,那就不好了想要查看工作表中的大量空白区域。

有什么解决办法吗?

有谁知道一些根据其内容大小设置 DragabbleScrollableSheet maxChildSize 的技巧或提供任何替代方案来解决此问题?

测试项目

我创建了一个小应用程序只是为了演示该问题。

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"),
            ),
          ],
        ),
      ),
    );
  }
}
  • 我尝试使用 GlobalKey 来获取 Listview 的大小,我认为这是不可能的,因为它有问题而且速度很慢。
  • 我也尝试过 LayoutBuilder,但没有产生严重的结果。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

姐不稀罕2025-01-23 21:28:41

我意识到问题是你无法在 DraggableScrollableSheet 中正确测量 ListView 大小,所以我想出了一个想法,也许我应该在其他地方测量 ListView 。

这是我想出的办法,虽然效率很低,但目前总比没有好。我也想知道更好的解决方案。

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

更新

如果您的内容大小发生变化,您仍然希望解决该问题。因此,您必须在构建期间或构建之后测量大小,并在构建中提供 PostFrameCallback 以显示正确大小的 DraggableScrollableSheet。所以最终你的代码将如下所示:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _key = GlobalKey();
  Size? _size;
  double neWheight = 100.0;
  bool weClicked = false;

  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) {
    print("build");

    final height = MediaQuery.of(context).size.height;
    weClicked ? SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
      print("schedule");
      _size = _key.currentContext?.size;
      showModalBottomSheet(
        isScrollControlled: true,
        context: context,
        builder: (_) => DraggableScrollableSheet(
          initialChildSize: getTheRightSize(height),
          minChildSize: 0.3,
          maxChildSize: getTheRightSize(height),
          expand: false,
          builder: (_, controller) => 
            MeasuredWidget(controller: controller, height: neWheight)
          )
      );
    }) : Container(); 
    return Scaffold(
      body: Stack(
        children: [
          Opacity(    
            key: _key,
            opacity: 0.0,
            child: MeasuredWidget(height: neWheight),
          ),
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      neWheight = 100;
                      weClicked = true;
                    });
                  },
                  child: const Text("Show scrollable sheet"), 
                ),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      weClicked = true;
                      neWheight = 200;
                    });
                  },
                  child: const Text("Show double scrollable sheet"),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

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

Update:

You still want to work around the problem if your content size changes. For this reason you have to measure size during build or after build and provide PostFrameCallback in build to show the correct sized DraggableScrollableSheet. So eventually your code would look like this:

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _key = GlobalKey();
  Size? _size;
  double neWheight = 100.0;
  bool weClicked = false;

  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) {
    print("build");

    final height = MediaQuery.of(context).size.height;
    weClicked ? SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
      print("schedule");
      _size = _key.currentContext?.size;
      showModalBottomSheet(
        isScrollControlled: true,
        context: context,
        builder: (_) => DraggableScrollableSheet(
          initialChildSize: getTheRightSize(height),
          minChildSize: 0.3,
          maxChildSize: getTheRightSize(height),
          expand: false,
          builder: (_, controller) => 
            MeasuredWidget(controller: controller, height: neWheight)
          )
      );
    }) : Container(); 
    return Scaffold(
      body: Stack(
        children: [
          Opacity(    
            key: _key,
            opacity: 0.0,
            child: MeasuredWidget(height: neWheight),
          ),
          Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      neWheight = 100;
                      weClicked = true;
                    });
                  },
                  child: const Text("Show scrollable sheet"), 
                ),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      weClicked = true;
                      neWheight = 200;
                    });
                  },
                  child: const Text("Show double scrollable sheet"),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}
初心未许2025-01-23 21:28:41

我可能有一个解决方案。我在 SingleChildListViewMeasureSize 小部件中使用了 Column 来自 此处获取列的高度。列渲染后,获取列的大小,并相应调整 DragabbleScrollableSheet 的最大高度。初始工作表高度设置为 0,并且使用draggableScrollController 可以在渲染列后将动画设置为 _maxSize。

解决方案

class BottomSheet extends StatefulWidget {
  const BottomSheet({required this.children, Key? key}) : super(key: key);

  List<Widget> children;

  @override
  State<BottomSheet> createState() => _BottomSheetState();
}

class _BottomSheetState extends State<BottomSheet> {

  DraggableScrollableController draggableScrollController = DraggableScrollableController();
  double _maxSize = 1;

  void _setMaxChildSize(Size size) {
    setState(() {
      // get height of the container.
      double boxHeight = size.height;
      // get height of the screen from mediaQuery.
      double screenHeight = MediaQuery.of(context).size.height;
      // get the ratio to set as max size.
      double ratio = boxHeight/screenHeight;
      _maxSize = ratio;
      _animateToMaxHeight(ratio);
    });
  }

  void _animateToMaxHeight(double ratio) {
    draggableScrollController.animateTo(ratio, duration: Duration(seconds: 1), curve: Curves.linear);
  }

  @override
  Widget build(BuildContext context) {
    return DraggableScrollableSheet(
        controller: draggableScrollController,
        initialChildSize: 0,
        minChildSize: 0.3,
        maxChildSize: _maxSize,
        expand: false,
        builder: (_, scrollController) =>
            SingleChildScrollView(
              controller: scrollController,
              child: MeasureSize(
                onChange: _setMaxChildSize,
                child: Column(
                  children: widget.children;
                ),
              ),
            ),
    );

  }
}

测量尺寸

为了完整起见,我还在此处提供了测量尺寸小部件。

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key? key,
    required this.onChange,
    required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }

  @override
  void updateRenderObject(
      BuildContext context, covariant MeasureSizeRenderObject renderObject) {
    renderObject.onChange = onChange;
  }
}

背景:

在 flutter 中获取(子)widget 的高度并不容易,只能在 widget 渲染后才能完成。
直接使用全局键获取 ListView 的大小似乎以某种方式返回 0。(我尝试过使用全局键并获取渲染框。)因此我使用单个 SingleChildScrollViewColumn 作为子项。这样就可以获取 Column 的大小,但只能在呈现 Column 的布局之后才能获取。

I might have a solution. I used a Column in SingleChildListView and MeasureSize widget from here to get the height of the column. After the column is rendered the size of the column is obtained and the maximum height of the DragabbleScrollableSheet is adjusted accordingly. The initial sheet height is set to 0 and using the draggableScrollController it is possible to animate to the _maxSize After the Column is rendered.

The solution

class BottomSheet extends StatefulWidget {
  const BottomSheet({required this.children, Key? key}) : super(key: key);

  List<Widget> children;

  @override
  State<BottomSheet> createState() => _BottomSheetState();
}

class _BottomSheetState extends State<BottomSheet> {

  DraggableScrollableController draggableScrollController = DraggableScrollableController();
  double _maxSize = 1;

  void _setMaxChildSize(Size size) {
    setState(() {
      // get height of the container.
      double boxHeight = size.height;
      // get height of the screen from mediaQuery.
      double screenHeight = MediaQuery.of(context).size.height;
      // get the ratio to set as max size.
      double ratio = boxHeight/screenHeight;
      _maxSize = ratio;
      _animateToMaxHeight(ratio);
    });
  }

  void _animateToMaxHeight(double ratio) {
    draggableScrollController.animateTo(ratio, duration: Duration(seconds: 1), curve: Curves.linear);
  }

  @override
  Widget build(BuildContext context) {
    return DraggableScrollableSheet(
        controller: draggableScrollController,
        initialChildSize: 0,
        minChildSize: 0.3,
        maxChildSize: _maxSize,
        expand: false,
        builder: (_, scrollController) =>
            SingleChildScrollView(
              controller: scrollController,
              child: MeasureSize(
                onChange: _setMaxChildSize,
                child: Column(
                  children: widget.children;
                ),
              ),
            ),
    );

  }
}

Measured size

For completeness I also provide the Measure Size widget here.

class MeasureSize extends SingleChildRenderObjectWidget {
  final OnWidgetSizeChange onChange;

  const MeasureSize({
    Key? key,
    required this.onChange,
    required Widget child,
  }) : super(key: key, child: child);

  @override
  RenderObject createRenderObject(BuildContext context) {
    return MeasureSizeRenderObject(onChange);
  }

  @override
  void updateRenderObject(
      BuildContext context, covariant MeasureSizeRenderObject renderObject) {
    renderObject.onChange = onChange;
  }
}

Background:

Getting the height of a (child) widget in flutter is not easy and can only be done after the widget is rendered.
Getting the size of the ListView directly with a global key somehow seems to return 0. (I have tried with global key and getting the render box.) Therefore I use a single SingleChildScrollView with a Column as child. This makes it possible to obtain the size of the Column, but only after Column's layout rendered.

拿命拼未来2025-01-23 21:28:41

将控制器定义为 DraggableScrollableSheet

var dragController = DraggableScrollableController();

然后你可以像这样使用它:

DraggableScrollableSheet(
                  controller: controller.dragController,
                  initialChildSize: 0.25,
                  minChildSize: 0.25,
                  maxChildSize: 1,

                  /** Your code Here **/
          ],
        ),

现在你可以获取 DraggableScrollableSheet 的大小
dragController.size 从 0 到 1

在此处输入图像描述

Define controller to DraggableScrollableSheet

var dragController = DraggableScrollableController();

then you can use it like this:

DraggableScrollableSheet(
                  controller: controller.dragController,
                  initialChildSize: 0.25,
                  minChildSize: 0.25,
                  maxChildSize: 1,

                  /** Your code Here **/
          ],
        ),

now you can get the size of DraggableScrollableSheet
dragController.size from 0 to 1

enter image description here

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文