跳转至正文

等间距列表项

如何创建等间距或扩展的列表项

你也许想创建一个列表,让所有列表项均匀分布,从而占满可见空间。例如,下图中的四个列表项均匀分布,「Item 0」在顶部,「Item 3」在底部。

Spaced items

同时,当列表项放不下时(例如设备太小、用户调整了窗口大小,或列表项数量超出屏幕),你可能还希望允许用户滚动浏览列表。

Scrollable items

通常你会用 Spacer 调整 widget 之间的间距,或用 Expanded 让 widget 占满可用空间。但在可滚动 widget 内部无法使用这些方案,因为它们需要有限的高度约束。

本食谱演示如何用 LayoutBuilderConstrainedBox,在空间足够时均匀分布列表项,在空间不足时允许用户滚动,步骤如下:

  1. 添加带 SingleChildScrollViewLayoutBuilder

  2. SingleChildScrollView 内添加 ConstrainedBox

  3. 创建带等间距列表项的 Column

1. 添加带 SingleChildScrollViewLayoutBuilder

#

首先创建 LayoutBuilder。你需要提供一个带两个参数的 builder 回调函数:

  1. LayoutBuilder 提供的 BuildContext

  2. 父 widget 的 BoxConstraints

在本食谱中你不会用到 BuildContext, 但下一步会用到 BoxConstraints

builder 函数中返回 SingleChildScrollView。该 widget 确保子 widget 可滚动,即使父容器太小也是如此。

dart
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(child: Placeholder());
  },
);

2. 在 SingleChildScrollView 内添加 ConstrainedBox

#

在本步中,将 ConstrainedBox 作为 SingleChildScrollView 的子 widget 添加。

ConstrainedBox widget 会为其子 widget 施加额外约束。

minHeight 参数设为 LayoutBuilder 约束的 maxHeight 来配置约束。

这确保子 widget 的最小高度等于 LayoutBuilder 约束提供的可用空间,即 BoxConstraints 的最大高度。

dart
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraints.maxHeight),
        child: Placeholder(),
      ),
    );
  },
);

但不要设置 maxHeight 参数,因为当列表项放不下屏幕时,你需要允许子 widget 大于 LayoutBuilder 的尺寸。

3. 创建带等间距列表项的 Column

#

最后,将 Column 作为 ConstrainedBox 的子 widget 添加。

要均匀分布列表项,将 mainAxisAlignment 设为 MainAxisAlignment.spaceBetween

dart
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraints.maxHeight),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            ItemWidget(text: 'Item 1'),
            ItemWidget(text: 'Item 2'),
            ItemWidget(text: 'Item 3'),
          ],
        ),
      ),
    );
  },
);

或者,你可以用 Spacer widget 调整列表项之间的间距,或用 Expanded widget 让某个 widget 占用比其他项更多的空间。

为此,你需要用 IntrinsicHeight widget 包裹 Column,它会强制 Column 按最小高度确定自身尺寸,而不是无限扩展。

dart
LayoutBuilder(
  builder: (context, constraints) {
    return SingleChildScrollView(
      child: ConstrainedBox(
        constraints: BoxConstraints(minHeight: constraints.maxHeight),
        child: IntrinsicHeight(
          child: Column(
            children: [
              ItemWidget(text: 'Item 1'),
              Spacer(),
              ItemWidget(text: 'Item 2'),
              Expanded(child: ItemWidget(text: 'Item 3')),
            ],
          ),
        ),
      ),
    );
  },
);

交互式样例

#

本示例展示在 Column 内均匀分布的列表项。当列表项放不下屏幕时,列表可以上下滚动。列表项数量由变量 items 定义,修改该值可观察列表项放不下屏幕时的表现。

import 'package:flutter/material.dart';

void main() => runApp(const SpacedItemsList());

class SpacedItemsList extends StatelessWidget {
  const SpacedItemsList({super.key});

  @override
  Widget build(BuildContext context) {
    const items = 4;

    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        cardTheme: CardThemeData(color: Colors.blue.shade50),
      ),
      home: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraints) {
            return SingleChildScrollView(
              child: ConstrainedBox(
                constraints: BoxConstraints(minHeight: constraints.maxHeight),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: List.generate(
                    items,
                    (index) => ItemWidget(text: 'Item $index'),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

class ItemWidget extends StatelessWidget {
  const ItemWidget({super.key, required this.text});

  final String text;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: SizedBox(height: 100, child: Center(child: Text(text))),
    );
  }
}