Flutter灵活布局,垂直溢出

Flutter flexible layout, vertical overflow

我正在开始我的第一个真实世界的 flutter 应用程序,到目前为止,我还不能全神贯注于如何解决我所拥有的这个非常基本的用例。

网上找了各种资料,但是none还真的解决了。这个用例对我来说似乎很常见,所以必须有一个很好的标准方法来做到这一点。

我有一个栏布局。为了说明这一点,有一个徽标(蓝色)、一个表单(红色)、一个按钮(绿色)和底部的一些自定义导航(黑色)。

代码如下:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.amber,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(32.0),
          child: Column(
            children: [
              const Placeholder(
                color: Colors.blue,
                fallbackHeight: 100.0,
              ),
              const Padding(
                padding: EdgeInsets.symmetric(vertical: 32.0),
                child: Placeholder(
                  color: Colors.red,
                  fallbackHeight: 300.0,
                ),
              ),
              const Spacer(),
              Column(
                children: [
                  const Placeholder(
                    color: Colors.green,
                    fallbackHeight: 40.0,
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 40.0),
                    child: const Placeholder(
                      color: Colors.black,
                      fallbackHeight: 40.0,
                    ),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

我在 Pixel 5 模拟器上运行。尺寸不是 100% 准确,但足以解释我认为的情况。

此屏幕是具有类似屏幕的流程的一部分,因此我想从底部向上布局自定义导航和按钮。 IE。当我导航到下一个屏幕时,我希望按钮和导航位于同一位置。

从上到下的标志和主要内容也是如此。因此,我在中间放了一个 spacer,它将占用中间的 space。

问题。我在表单中进行了一些验证。当按下按钮并破坏验证时,表单将在每个字段下方显示一堆消息。我通过将表单占位符的高度增加到 500 来模拟这一点。

所以现在所有的组件都不再适合屏幕了,我在底部有一些溢出。没有什么奇怪的。 在研究解决这个问题的最惯用方法时,我发现它是添加一个有意义的 SingleChildScrollView

但是添加 SingleChildScrollView 会破坏代码,因为我在那里有一个 Spacer。我理解那部分。既然我们现在说“尽可能多地占用 space,我会让它滚动”,我们有无限的 space 供我们使用。同时 Spacer 基本上说,“我会尽可能地把所有东西都压低,直到没有 space 剩下”。所以这也是有道理的。让我们删除 spacer.

代码如下:

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.amber,
      body: SafeArea(
        child: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(32.0),
            child: Column(
              children: [
                const Placeholder(
                  color: Colors.blue,
                  fallbackHeight: 100.0,
                ),
                const Padding(
                  padding: EdgeInsets.symmetric(vertical: 32.0),
                  child: Placeholder(
                    color: Colors.red,
                    fallbackHeight: 500.0,
                  ),
                ),
                Column(
                  children: [
                    const Placeholder(
                      color: Colors.green,
                      fallbackHeight: 40.0,
                    ),
                    Container(
                      margin: const EdgeInsets.only(top: 40.0),
                      child: const Placeholder(
                        color: Colors.black,
                        fallbackHeight: 40.0,
                      ),
                    ),
                  ],
                )
              ],
            ),
          ),
        ),
      ),
    );
  }
}

它按预期工作。我没有收到任何错误,我可以滚动查看按钮和导航。

现在总结一下。当我的表单恢复到原来的高度 300px 时会发生什么?当然,因为 spacer 被删除了,我不再有我想要的那种行为,按钮和导航是从底部开始的。他们现在被拉到表格中。当然,这也是有道理的。

我明白为什么所有不同的场景都会如此。但是我怎样才能创建一个灵活的布局,其中一些东西从底部定位,一些从顶部定位,但仍然防止溢出?

我是否必须开始计算何时以及是否会发生溢出并动态添加滚动视图?我还没有走那条路,因为我希望有一种更明确、更标准的方式来处理这个问题。

这应该可以解决问题:

return Scaffold(
      appBar: AppBar(
        title: Text('Demo'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          Expanded(
              child: ListView(children: [
                Text('Logo'),
               SizedBox(height: 1000,), //large placeholder
                Text('bottom')
              ],)),
          ElevatedButton(onPressed: () {}, child: Text('Your button'))
        ],
      ),
    );

所以基本上你开始用你的按钮从底部填充你的屏幕,然后你用扩展的小部件填充剩余的可用 space,然后你用像 ListView 这样的可滚动小部件填充(对于多个子部件,这比 SingleChildscrollView 更好。

要将此概念应用到您的代码中:

return Scaffold(
      backgroundColor: Colors.amber,
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: [
            Expanded(
              child: SingleChildScrollView(
                child: Padding(
                  padding: const EdgeInsets.all(32.0),
                  child: Column(
                    children: [
                      const Placeholder(
                        color: Colors.blue,
                        fallbackHeight: 100.0,
                      ),
                      const Padding(
                        padding: EdgeInsets.symmetric(vertical: 32.0),
                        child: Placeholder(
                          color: Colors.red,
                          fallbackHeight: 500.0,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            Column(
              children: [
                const Placeholder(
                  color: Colors.green,
                  fallbackHeight: 40.0,
                ),
                Container(
                  margin: const EdgeInsets.only(top: 40.0),
                  child: const Placeholder(
                    color: Colors.black,
                    fallbackHeight: 40.0,
                  ),
                ),
              ],
            )
          ],
        ),
      ),
    );

试试下面的代码希望对你有帮助。将您的专栏添加到 SingleChildScrollView

Scaffold(
      backgroundColor: Colors.amber,
      body: SafeArea(
        child: Padding(
          padding: const EdgeInsets.all(32.0),
          child: SingleChildScrollView(
            child: Column(
              children: [
                const Placeholder(
                  color: Colors.blue,
                  fallbackHeight: 100.0,
                ),
                const Padding(
                  padding: EdgeInsets.symmetric(vertical: 32.0),
                  child: Placeholder(
                    color: Colors.red,
                    fallbackHeight: 900.0,
                  ),
                ),
                SingleChildScrollView(
                  child: Column(
                    children: [
                      const Placeholder(
                        color: Colors.green,
                        fallbackHeight: 40.0,
                      ),
                      Container(
                        margin: const EdgeInsets.only(top: 40.0),
                        child: const Placeholder(
                          color: Colors.black,
                          fallbackHeight: 40.0,
                        ),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
      // If you want your bottom widget is constant then used bottomSheet (uncomment below bottomSheet code)
      /*
      bottomSheet: Container(
          height: 40,
          color: Colors.red,
        ),
     */
    );