【问题标题】:Overflowing parent widgets溢出的父小部件
【发布时间】:2018-04-25 12:50:43
【问题描述】:

我正在尝试创建一个具有按钮的小部件,只要按下该按钮,就会在其下方打开一个列表,填充按钮下方的所有空间。我用一个简单的Column 实现了它,如下所示:

class _MyCoolWidgetState extends State<MyCoolWidget> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Column(
      children: <Widget>[
        new MyButton(...),
        isPressed ? new Expanded(
          child: new SizedBox(
            width: MediaQuery.of(context).size.width,
            child: new MyList()
          )
        ) : new Container()
      ]
    )
  }
}

这在很多情况下都可以正常工作,但不是全部。

问题我在创建此小部件时遇到的问题是,如果将 MyCoolWidget 放在 Row 内,例如与其他小部件一起使用,那么可以说其他 MyCoolWidgets,列表受Row 所暗示的宽度限制。
我尝试使用OverflowBox 解决此问题,但不幸的是没有运气。

此小部件与选项卡的不同之处在于它们可以放置在小部件树中的任何位置,当按下按钮时,即使这意味着忽略约束,列表也会填满按钮下的所有空间。

下图是我试图在Row 中的“BUTTON1”和“BUTTON2”或MyCoolWidgets 中实现的效果:

编辑:实际代码片段

class _MyCoolWidgetState extends State<MyCoolWidget> {

  bool isTapped = false;

  @override
  Widget build(BuildContext context) {
    return new Column(
      children: <Widget>[
        new SizedBox(
          height: 20.0,
          width: 55.0,
          child: new Material(
            color: Colors.red,
            child: new InkWell(
              onTap: () => setState(() => isTapped = !isTapped),
              child: new Text("Surprise"),
            ),
          ),
        ),
        bottomList()
      ],
    );
  }

  Widget comboList() {
    if (isTapped) {
      return new Expanded(
        child: new OverflowBox(
          child: new Container(
            color: Colors.orange,
            width: MediaQuery.of(context).size.width,
            child: new ListView( // Random list
              children: <Widget>[
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
                new Text("ok"),
              ],
            )
          )
        ),
      );
    } else {
      return new Container();
    }
  }
}

我是这样使用的:

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Row(
      children: <Widget>[
        new Expanded(child: new MyCoolWidget()),
        new Expanded(child: new MyCoolWidget()),
      ]
    )
  }
}

以下是代码实际执行的屏幕截图:

【问题讨论】:

  • 你能链接一个工作的代码吗?这样我们就可以自己重现错误。
  • @RémiRousselet 已添加!
  • @RémiRousselet 又改了一点,这样你就可以简单地复制粘贴来复制了:)
  • 你可以发布包含按钮的“工作”代码而不是溢出的东西吗?溢出不是解决方案。
  • @RémiRousselet 这是我用于我要创建的小部件的代码,唯一的区别是我将按钮代码从单独的类中提取到 Column 并创建一个虚拟列表。我连续只有两个这样的小部件,添加行的代码会有帮助吗?

标签: dart flutter


【解决方案1】:

从 cmets 澄清说,OP 想要的是这样的:

制作一个覆盖所有内容的弹出窗口,从屏幕上按钮的任何位置到屏幕底部,同时水平填充它,无论按钮在屏幕上的哪个位置。按下按钮时,它也会切换打开/关闭。

有几种方法可以做到这一点;最基本的是使用 Dialog 和 showDialog,但它在 SafeArea 周围存在一些问题,这使得这变得困难。此外,OP 要求按钮进行切换,而不是按对话框以外的任何位置(这是对话框所做的 - 或者阻止对话框后面的触摸)。

这是一个如何执行此类操作的工作示例。完全免责声明 - 我并不是说这是一件好事,甚至不是一个好方法……但它是一种 方法。

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

// We're extending PopupRoute as it (and ModalRoute) do a lot of things
// that we don't want to have to re-create. Unfortunately ModalRoute also
// adds a modal barrier which we don't want, so we have to do a slightly messy
// workaround for that. And this has a few properties we don't really care about.
class NoBarrierPopupRoute<T> extends PopupRoute<T> {
  NoBarrierPopupRoute({@required this.builder});

  final WidgetBuilder builder;

  @override
  Color barrierColor;

  @override
  bool barrierDismissible = true;

  @override
  String barrierLabel;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
    return new Builder(builder: builder);
  }

  @override
  Duration get transitionDuration => const Duration(milliseconds: 100);

  @override
  Iterable<OverlayEntry> createOverlayEntries() sync* {
    // modalRoute creates two overlays - the modal barrier, then the
    // actual one we want that displays our page. We simply don't
    // return the modal barrier.
    // Note that if you want a tap anywhere that isn't the dialog (list)
    // to close it, then you could delete this override.
    yield super.createOverlayEntries().last;
  }

  @override
  Widget buildTransitions(
      BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    // if you don't want a transition, remove this and set transitionDuration to 0.
    return new FadeTransition(opacity: new CurvedAnimation(parent: animation, curve: Curves.easeOut), child: child);
  }
}

class PopupButton extends StatefulWidget {
  final String text;
  final WidgetBuilder popupBuilder;

  PopupButton({@required this.text, @required this.popupBuilder});

  @override
  State<StatefulWidget> createState() => PopupButtonState();
}

class PopupButtonState extends State<PopupButton> {
  bool _active = false;

  @override
  Widget build(BuildContext context) {
    return new FlatButton(
      onPressed: () {
        if (_active) {
          Navigator.of(context).pop();
        } else {
          RenderBox renderbox = context.findRenderObject();
          Offset globalCoord = renderbox.localToGlobal(new Offset(0.0, context.size.height));
          setState(() => _active = true);
          Navigator
              .of(context, rootNavigator: true)
              .push(
                new NoBarrierPopupRoute(
                  builder: (context) => new Padding(
                        padding: new EdgeInsets.only(top: globalCoord.dy),
                        child: new Builder(builder: widget.popupBuilder),
                      ),
                ),
              )
              .then((val) => setState(() => _active = false));
        }
      },
      child: new Text(widget.text),
    );
  }
}

class MyApp extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new SafeArea(
        child: new Container(
          color: Colors.white,
          child: new Column(children: [
            new PopupButton(
              text: "one",
              popupBuilder: (context) => new Container(
                    color: Colors.blue,
                  ),
            ),
            new PopupButton(
              text: "two",
              popupBuilder: (context) => new Container(color: Colors.red),
            )
          ]),
        ),
      ),
    );
  }
}

对于更古怪的建议,您可以使用查找位置部分并查看this answer which describes how to create a child that isn't constrained by it's parent's position

但是你最终会这样做,最好不要让列表成为按钮的直接子元素,因为 Flutter 中的很多东西都取决于子元素的大小,并使其能够扩展到全屏尺寸很容易引起问题。

【讨论】:

  • 这完全没问题!但是OverlayRoute 类不是比PopupRoute 更适合吗?
  • 还有一个问题:yield super.createOverlayEntries().last; 到底是做什么的?它可能需要最后一个OverlayEntry,但那是显示我的小部件的叠加层吗?如果是这样,这将始终有 2 OverlayEntries 是正确的吗?一个用于模态障碍,一个用于实际内容/小部件?
  • PopupRoute 是 ModalRoute 的子类,ModalRoute 是 TransitionRoute 的子类,TransitionRoute 是 OverlayRoute 的子类。它们中的每一个都添加了一些额外的功能——TransitionRoute 的转换、后退按钮和 ModalRoute 的其他一些功能,尽管我可能已经跳过了 PopupRoute。这实际上是我在 Flutter 的实现中并不为之疯狂的一件事,他们如何将路由的实现绑定到一个长链上,每个长链都实现了一些东西,而不是说一个带有一堆 mixin 的类。特长;如果你扩展 OverlayRoute,你必须实现更多。
  • 是的,那部分有点乱,但是 ModalRoute 应该总是返回 2 个覆盖条目——第一个是障碍,第二个是你真正想要显示的(这将永远是最后一个,因为最后位于顶部;p)。使用 yield 只是我懒惰,我可以不使用 sync* 并返回一个列表/其他迭代,其中只包含超级交互中的最后一个元素。
猜你喜欢
  • 2020-02-17
  • 2017-08-06
  • 2021-10-15
  • 2012-05-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
相关资源
最近更新 更多