在这里插入图片描述

Flutter for OpenHarmony 实战之基础组件:第十四篇 Expanded, Flexible 与 Spacer 弹性布局

前言

如果你是 Flutter 新手,你一定遇到过那个令人抓狂的黑黄条纹警告:
Bottom overflowed by 20 pixels.

这通常意味着你的组件尺寸超出了屏幕边界。在 Row 或 Column 中,子组件默认是“能占多大就占多大”或者“固定多大就多大”。

如果我们需要实现:

  • 左边图片固定宽,右边文字自动填满剩余空间
  • 上中下三块区域,按照 1:2:1 的比例分配高度?

这时,弹性布局(Flex Layout)就是唯一的救星。

本文你将学到

  • Expanded vs Flexible 的核心区别
  • flex 系数的数学原理 (2:1 比例怎么写)
  • Spacer:最优雅的留白方式
  • 鸿蒙适配:利用 Flex 实现平板上的左右分栏布局

一、三剑客详解

这三个组件都必须用在 RowColumnFlex 的直系子级中,否则会报错。

1.1 Expanded:霸道总裁 (强制撑满)

在这里插入图片描述

Expanded 会强迫子组件占满所有剩余空间。无论子组件自己想要多宽,都会被忽略,直接拉伸。

Row(
  children: [
    Container(width: 50, color: Colors.blue), // 固定宽
    Expanded(
      child: Container(color: Colors.green), // 💡 霸占剩下的所有宽度
    ),
    Container(width: 50, color: Colors.blue), // 固定宽
  ],
)

1.2 Flexible:佛系青年 (自适应)

在这里插入图片描述

Flexible 也会让子组件占满剩余空间,但它有一个 fit 属性:

  • FlexFit.loose (默认):如果子组件本身很小(比如只有 10px),那就只占 10px,剩下的空间空着。
  • FlexFit.tight:等同于 Expanded,强迫撑满。

场景对比
当你想让一段长文字自动换行而不溢出屏幕时,用 ExpandedFlexible 均可(通常用 Expanded)。

1.3 Spacer:透明空气 (占位)

在这里插入图片描述

Spacer 本质上就是一个空的 Expanded。它专门用来在组件之间制造弹性的空白。

Row(
  children: [
    Text('标题'),
    Spacer(), // 💡 自动把标题顶到左边,按钮顶到右边
    ElevatedButton(onPressed: (){}, child: Text('更多')),
  ],
)

二、Flex 系数:比例的艺术

在这里插入图片描述

如果有多个弹性组件,如何分配空间?通过 flex 属性(整数)。默认是 1。

案例:黄金比例分割

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Flex 比例分配')),
      body: Column(
        children: [
          const Padding(
            padding: EdgeInsets.all(16.0),
            child: Text('上下高度比例 1 : 2 : 1',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          ),
          Expanded(
            flex: 1,
            child: Container(
              color: Colors.red[200],
              alignment: Alignment.center,
              child: const Text('Flex: 1', style: TextStyle(fontSize: 20)),
            ),
          ),
          Expanded(
            flex: 2,
            child: Container(
              color: Colors.blue[200],
              alignment: Alignment.center,
              child: const Text('Flex: 2', style: TextStyle(fontSize: 20)),
            ),
          ),
          Expanded(
            flex: 1,
            child: Container(
              color: Colors.green[200],
              alignment: Alignment.center,
              child: const Text('Flex: 1', style: TextStyle(fontSize: 20)),
            ),
          ),
        ],
      ),
    );
  }
}

原理:系统会把所有 flex 值相加 (1+2=3),然后按份数分配。


三、OpenHarmony 鸿蒙适配专题

在这里插入图片描述

3.1 左右分栏布局 (Master-Detail UI)

在鸿蒙平板或折叠屏上,常见的布局是:左侧导航栏(固定宽度),右侧内容区(自适应撑满)。

这种经典的布局结构,正是 Row + Expanded 的最佳舞台。

class TabletLayout extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          // 1. 左侧侧边栏:固定宽度 250
          Container(
            width: 250,
            color: Colors.grey[200],
            child: ListView(children: [/* 菜单项 */]),
          ),
          
          // 2. 右侧内容区:自适应撑满
          Expanded(
            child: Container(
              color: Colors.white,
              child: Center(child: Text('详细内容区域')),
            ),
          ),
        ],
      ),
    );
  }
}

四、实战:仿“微信朋友圈”条目布局

在这里插入图片描述

朋友圈的一条动态包含:头像、昵称、内容、图片、时间、点赞按钮。这是一个非常复杂的嵌套布局。

难点:时间在左,点赞按钮在右,且中间距离不固定。

Row(
  crossAxisAlignment: CrossAxisAlignment.start, // 顶部对齐
  children: [
    // 1. 头像 (固定)
    Container(
      width: 44, height: 44,
      decoration: BoxDecoration(color: Colors.grey, borderRadius: BorderRadius.circular(4)),
    ),
    SizedBox(width: 10),
    
    // 2.右侧内容区域 (自适应撑满)
    Expanded(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('微信昵称', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold)),
          SizedBox(height: 4),
          Text('今天天气真不错,Flutter 布局真好用!', style: TextStyle(fontSize: 15)),
          SizedBox(height: 10),
          
          // 底部栏:时间 + Spacer + 点赞
          Row(
            children: [
              Text('1小时前', style: TextStyle(fontSize: 12, color: Colors.grey)),
              Spacer(), // 💡 自动挤开中间空间
              Icon(Icons.more_horiz, color: Colors.blueGrey),
            ],
          ),
        ],
      ),
    ),
  ],
)

五、总结

弹性布局是解决屏幕适配问题的银弹。

核心要点

  1. 原则:在 Row/Column 里,只要子组件宽度/高度不确定,就包一层 Expanded
  2. 比例:用 flex 属性轻松实现 1:1, 1:2 等比例分割。
  3. 留白:用 Spacer 代替 MainAxisAlignment.spaceBetween,代码更具可读性。
  4. 警告:千万别在 Expanded 外部再包 Container 限制宽高,这会破坏弹性机制。

下一篇预告

终于讲完了这些基础的“方块”组件。接下来,我们将把前面学到的所有 14 篇知识融会贯通,来一场激动人心的大练兵
《Flutter for OpenHarmony 实战之基础组件:第十五篇 综合案例 —— 打造高颜值“个人中心”页》
我们将从零开始,手写一个包含头像、波浪背景、功能列表的完整页面,为基础篇画上完美的句号。


🌐 欢迎加入开源鸿蒙跨平台社区开源鸿蒙跨平台开发者社区

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐