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

问题描述 投票:0回答:3

问题

所以基本上这是一个相当老的问题,我无法通过谷歌解决。问题是

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,但没有产生严重的结果。
flutter dart draggable bottom-sheet modal-sheet
3个回答
1
投票

我意识到问题是你无法在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"),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

0
投票

将控制器定义为

DraggableScrollableSheet

var dragController = DraggableScrollableController();

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

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

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

现在可以获取DraggableScrollableSheet的大小了

dragController.size
从0到1


0
投票

我可能有一个解决方案。我在

Column
here
中使用了
SingleChildListView
MeasureSize 小部件来获取列的高度。渲染列后,获取列的大小,并相应调整 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;
                ),
              ),
            ),
    );

  }
}

测量尺寸

为了完整起见,我还在此处提供了 Measure Size 小部件。

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。(我尝试过使用全局键并获取渲染框。)因此,我使用单个
SingleChildScrollView
Column
作为子项。这使得可以获得
Column
的大小,但只能在渲染
Column
的布局之后。

© www.soinside.com 2019 - 2024. All rights reserved.