【问题标题】:How to make a Widget to be as small as it can (wrap_content in Android)?如何使 Widget 尽可能小(Android 中的 wrap_content)?
【发布时间】:2020-04-05 05:52:35
【问题描述】:

我遇到了类似于Column>PageView>Column 的渲染树的渲染问题,其中最后一个ColumnPageView 的页面内。

PageView 未正确呈现,所以我得到一个异常 (Horizontal viewport was given unbounded height.),因为框架无法计算其大小,我可以通过将其包装成 Flexible 或 @987654335 来修复它@,但我不希望PageView 占据整个屏幕,我希望它尽可能小并位于屏幕中心。

这是我的问题的表示:

// This code throws an exception:

class Widget_1_Has_Problem extends StatelessWidget {
  final PageController pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(Icons.done,size: 112),
          SizedBox(height: 32),
          PageView(
            physics: NeverScrollableScrollPhysics(),
            controller: pageController,
            children: <Widget>[
            // Many widgets go here, I am just simplifying with a Column.
              Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
                  SizedBox(height: 16),
                  Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
                ],
              ),
            ],
          ),
        ],
      ),
    );
  }
}

这就是我想要实现的(我删除了PageView 以显示它):

class Widget_2_No_PageView extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Icon(Icons.done,size: 112),
          SizedBox(height: 32),
          Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
              SizedBox(height: 16),
              Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
            ],
          ),
        ],
      ),
    );
  }
}

这是我可以解决的方法,但它并不完美,我稍后会展示:

class Widget_3_With_Flex_Not_Perfect_Solution extends StatelessWidget {
  final PageController pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Spacer(flex: 2,),
          Icon(Icons.done,size: 112),
          SizedBox(height: 32),
          Flexible(
            flex: 1,
            child: PageView(
              physics: NeverScrollableScrollPhysics(),
              controller: pageController,
              children: <Widget>[
                Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: <Widget>[
                    Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
                    SizedBox(height: 16),
                    Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
                  ],
                ),
              ],
            ),
          ),
          Spacer(flex: 2,),
        ],
      ),
    );
  }
}

这个解决方案并不完美,因为SpacerFlexibleExpanded 总是试图保持它与flex 权重的比例,这意味着对于较小的显示器Spacer 不会消失,它将保留在那里,而第一个和所需的图像不会有这样的空白空间。如下图所示,Spacer 始终存在。此外,我必须为这个设计中的每一个变化计算 flex 因子,而在我的第一个代码示例中,小部件将自动调整到屏幕中心。

我怎样才能指示PageView 尽可能小,而不是随心所欲地扩展,因为这是我能在网上找到的唯一解决方案?

【问题讨论】:

  • 试过IntrinsicHeight?
  • 不,我没有,据我所知,IntrinsicHeight 使 Row 中的所有小部件都具有相同的大小
  • 文档说:“一个将其子级大小调整为子级固有高度的小部件。此类很有用,例如,当无限高度可用并且您想要一个孩子时尝试无限扩展以将自身调整到更合理的高度。”
  • 哦,这听起来很有希望,我只是从示例中知道的。命名应该更具描述性,“内在”是非常技术性的,大多数外国人甚至都不知道它是什么意思
  • @pskink 我刚刚尝试了IntrinsicHeight,但它不起作用......它对你有用吗?

标签: flutter flutter-layout


【解决方案1】:

这可以做到,但不能使用 PageView。我不得不将 SingleChildScrollView 与 Row 一起使用。这样做的原因是:如果您想正确调整 PageView 的大小,则需要构建和布局它拥有的每个子项。 PageView 不会这样做,但 Row 会这样做,您甚至可以决定应该如何定位较小的元素。如果您正确设置行中子项的宽度,PageController 也可以工作。

请注意,这只有在页面数量很少时才能很好地工作。

class SizingPageView extends StatelessWidget {
  final ScrollPhysics physics;
  final PageController controller;
  final List<Widget> children;

  const SizingPageView({Key key, this.physics, this.controller, this.children})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    Widget transform(Widget input) {
      return SizedBox(
        width: MediaQuery.of(context).size.width,
        child: input,
      );
    }

    return SingleChildScrollView(
      physics: physics ?? PageScrollPhysics(),
      controller: controller,
      scrollDirection: Axis.horizontal,
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: children.map(transform).toList(),
      ),
    );
  }
}

class Solution extends StatelessWidget {
  final PageController pageController = PageController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Spacer(
            flex: 2,
          ),
          Icon(Icons.done, size: 112),
          SizedBox(height: 32),
          Text('BEFORE SCROLL'),
          SizingPageView(
            physics: NeverScrollableScrollPhysics(),
            controller: pageController,
            children: <Widget>[
              Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
                  SizedBox(height: 16),
                  Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),
                ],
              ),
              Column(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
                ],
              ),
              Text('Third'),
            ],
          ),
          Text('AFTER SCROLL'),
          Spacer(
            flex: 2,
          ),
          FlatButton(
            child: Text('Next'),
            onPressed: () {
              var next = pageController.page.round() + 1;
              if (next >= 3) {
                next = 0;
              }
              pageController.animateToPage(
                next,
                duration: Duration(seconds: 1),
                curve: Curves.easeInOut,
              );
            },
          )
        ],
      ),
    );
  }
}

【讨论】:

  • @mFeinstein 抱歉,打错了。我的意思是它只适用于少量页面,因为 Flutter 需要对它们中的每一个进行布局以计算最终大小。
【解决方案2】:

您可以简单地使用 Flexible 小部件 https://api.flutter.dev/flutter/widgets/Flexible-class.html 包装小部件

【讨论】:

  • 阅读帖子,这是我做的第一件事。
  • 哈哈..对不起我忘了。很快就会给你完美的答案
【解决方案3】:

您要求 PageView 内的项目来设置 PageView 本身的大小。这不适用于标准类。如果你查看它的源代码,你会发现它使用了 Scrollable 中的 Viewport。视口获取最大尺寸并忽略子项(Slivers)的尺寸。您需要使用不同的方法,或者创建一个自定义 PageView,也许是使用 ShrinkWrappingViewport 的一个。

https://api.flutter.dev/flutter/widgets/ShrinkWrappingViewport-class.html

【讨论】:

  • Scrollable 小部件在页面视图中的每个页面都很常见。我们不能把一页变小,然后再把下一页变大。
【解决方案4】:

PageView 使用SliverFillViewport 环绕给定的children

这个SliverFillViewport 不在乎children 有多小。它只关心它可以从其父级占据多少空间。所以FlexAlign 小部件是没用的。

目前使用PageView,除非我们进行一些计算并使用SizedBoxConstrainedBox,否则这很可能是不可能的。

我也发现了这个discussion

【讨论】:

    【解决方案5】:

    通过将PageView 包裹在SizedBox 中,我们可以消除您遇到的异常。 (Horizontal viewport was given unbounded height.)。

    SizedBox(height: 32, child:
              PageView(
                physics: NeverScrollableScrollPhysics(),
                controller: pageController,
                children: <Widget>[
                // Many widgets go here, I am just simplifying with a Column.
                  Column(
                  ...
    

    但这会给你一个 16 像素的bottom overflow exception,因为你在PageView 中有另一个SizedBox,在Column 内具有固定高度。

    为了解决这个问题,让我们暂时将 top SizedBox 替换为 Container 并添加 color 属性,看看我们可以使用多少空间和高度。

    因此,我们可以使用MediaQuery 为我们计算高度,而不是将大小框高度硬编码为 32。

    如果你打印顶部的高度SizedBox,它将是597

    SizedBox(height: MediaQuery.of(context).size.height,

    我为了演示,将height 除以 5 得到包含PageViewSizedBox 小部件的高度为 119.4,这解决了渲染流异常并保持了页面浏览量在屏幕中心的位置。

    如果我们再次使用Container 来查看现在有多少空间和高度可供我们使用,它将如下所示:

    因此,使用这种方法,您将不需要使用您提到的 ExpandedFlexible 小部件,并且不需要更改大部分代码。

    工作代码如下:

    h = MediaQuery.of(context).size.height / 5;  
        print(h);  // 119.4
        return Scaffold(
          body: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Icon(Icons.done,size: 112),
              SizedBox(height: h, child:
              PageView(
                physics: NeverScrollableScrollPhysics(),
                controller: pageController,
                children: <Widget>[
                // Many widgets go here, I am just simplifying with a Column.
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: <Widget>[
                      Text('TEXT TEXT TEXT TEXT TEXT TEXT'),
                      SizedBox(height: 16),
                      Text('TEXT2 TEXT2 TEXT2 TEXT2 TEXT2 TEXT2'),   
                    ],
                  ),
                ],
              ),
             ),
            ],
          ),
        );
    

    您可能需要根据需要调整高度,但我希望这可以解决您的问题并回答您的问题。

    【讨论】:

    • 这不是我正在寻找的答案,我想要一些自动的东西,而不是我必须计算的东西,如果我添加一个新的小部件,我必须更改数字。整个MediaQuery 计算应该由带有布局小部件的框架完成,而不是我自己。这更像是一种解决方法而不是解决方案,我正在寻找“实现这一目标的正确方法”,而不是“如何让它发挥作用”。
    猜你喜欢
    • 1970-01-01
    • 2020-09-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-14
    • 1970-01-01
    • 2016-02-19
    • 1970-01-01
    相关资源
    最近更新 更多