第三讲 进阶布局与样式(精细化UI)

前言:

这一讲是对样式的讲解,可能多点布局的高阶组件,但用处不大。

在前面两讲中都有涉猎,对大局熟悉没有影响,跳过也没事,之后用到什么AI一下就都知道了。反正都在函数的入参里,知道是哪个widget,就很好找,很好理解,现在看了用少了也记不住。

之所以有这讲,也是考虑到,第一部分UI基石体系的全面性,做一个样式层面的end。

一、本章核心目标

本章聚焦 Flutter 中精细化控制UI布局与视觉表现的核心技术,解决基础布局无法满足的「精准间距控制、弹性空间分配、约束适配、视觉装饰」等问题。

通过掌握 Padding/Margin/Border、Flex/Expanded、BoxConstraints、LayoutBuilder 这些核心工具,你能从「能用」的布局升级为「好用、好看、适配全场景」的专业级UI实现,让界面既符合设计规范,又能自适应不同屏幕尺寸。

暂时无法在飞书文档外展示此内容

核心原理解读

  1. 约束是布局的「规则上限」 :父组件通过 BoxConstraints 告诉子组件「你最大/最小能多大」(紧约束:固定尺寸;松约束:范围尺寸);
  2. 装饰是「视觉包装」:Padding/Margin/Border 不改变组件的核心尺寸计算逻辑,仅在组件绘制时增加「内边距/外边距/边框」的视觉区域;
  3. 弹性布局是「空间分配策略」:Flex(Row/Column)+ Expanded 解决「剩余空间如何分配」的问题,让多个子组件按比例占用空间;
  4. LayoutBuilder 是「约束感知工具」:能主动获取父组件的约束,实现「不同约束下展示不同布局」的自适应效果。

二、核心技术点

1. Padding、Margin、Border(装饰与间距)

核心属性
组件/属性 作用 关键参数
Padding 内边距(组件内容与边界的间距) padding: EdgeInsets(all/symmetric/only)
Margin(Container属性) 外边距(组件与其他组件的间距) margin: EdgeInsets(同Padding)
Border 边框装饰 border: Border.all(width, color)borderRadius
案例代码
less 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('间距与边框装饰')),
        body: Center(
          child: Container(
            // 外边距:与父组件(Center)的间距
            margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
            // 内边距:内容与Container边界的间距
            padding: const EdgeInsets.all(15),
            // 边框+圆角装饰
            decoration: BoxDecoration(
              border: Border.all(width: 2, color: Colors.blue),
              borderRadius: BorderRadius.circular(10),
            ),
            child: const Text(
              '精细化UI装饰',
              style: TextStyle(fontSize: 20),
            ),
          ),
        ),
      ),
    );
  }
}
注意事项
  • Padding 是独立组件,marginContainer 的属性(本质是给Container套了一层Padding);
  • Border 必须配合 BoxDecoration 使用,不能直接作为组件属性;
  • EdgeInsets.only() 可精准控制单方向间距(如 left: 10, top: 5),适配非对称布局;
  • 圆角边框(borderRadius)与 BoxDecorationcolor/image 兼容,但不能与 BoxShape.circle 同时使用。

2. Flex、Expanded(弹性空间分配)

核心属性
组件/属性 作用 关键参数
Flex/Row/Column 弹性布局容器(Row=水平Flex,Column=垂直Flex) direction(主轴方向)、mainAxisAlignment(主轴对齐)、crossAxisAlignment(交叉轴对齐)
Expanded 抢占Flex容器的剩余空间 flex: int(空间占比,默认1)
案例代码
less 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('弹性空间分配')),
        body: Padding(
          padding: const EdgeInsets.all(20),
          // Row = 水平方向的Flex
          child: Row(
            // 主轴(水平)对齐方式:均分剩余空间
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              // 不占剩余空间,仅包裹内容
              const Text('固定宽度'),
              // 占剩余空间的1份
              Expanded(
                flex: 1,
                child: Container(
                  height: 40,
                  color: Colors.red,
                  child: const Center(child: Text('占1份')),
                ),
              ),
              // 占剩余空间的2份
              Expanded(
                flex: 2,
                child: Container(
                  height: 40,
                  color: Colors.blue,
                  child: const Center(child: Text('占2份')),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
注意事项
  • Expanded 只能作为 Flex/Row/Column 的直接子组件,否则会报错;
  • flex 是「比例」而非「固定尺寸」,比如 flex:1 和 flex:2 会按 1:2 分配剩余空间;
  • 避免在 Expanded 中嵌套无限宽/高的组件(如未设置width的TextField),会导致布局溢出。

3. BoxConstraints(紧/松约束)

核心概念
约束类型 定义 示例场景
紧约束 固定宽高(min=max=具体值) Container(width: 100, height: 50)
松约束 宽高有范围(min≠max) Container(width: double.infinity, height: 50)(宽度占满父容器)
案例代码
less 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('紧/松约束')),
        body: Center(
          child: Container(
            // 父容器:松约束(宽度最大300,高度固定80)
            constraints: BoxConstraints(
              maxWidth: 100,
              minHeight: 80,
              maxHeight: 100,
            ),
            color: Colors.red[200],
            child: Container(
              // 子容器:紧约束(固定宽高)
              constraints: BoxConstraints.tight(Size(3000, 60)),
              color: Colors.green,
              child: const Center(child: Text('紧约束子组件')),
            ),
          ),
        ),
      ),
    );
  }
}
注意事项
  • BoxConstraints 可通过 constraints 属性直接设置给Container,优先级高于直接设置的width/height;
  • 子组件的约束不能超过父组件的约束(比如父maxWidth=100,子width=3000会被限制为100);
  • BoxConstraints.expand() 是松约束的常用写法,等价于 width: double.infinity, height: double.infinity(占满父容器)。

4. LayoutBuilder(获取父约束)

核心作用

LayoutBuilder 能捕获父组件传递的约束(BoxConstraints),根据约束动态调整布局(比如「窄屏垂直排列、宽屏水平排列」)。

案例代码
less 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('LayoutBuilder 约束感知')),
        body: Padding(
          padding: const EdgeInsets.all(10),
          // LayoutBuilder捕获父组件的约束
          child: LayoutBuilder(
            builder: (context, constraints) {
              // 获取父容器的最大宽度
              double parentWidth = constraints.maxWidth;
              // 宽屏(>600):水平排列;窄屏:垂直排列
              bool isWideScreen = parentWidth > 600;

              return Container(
                color: Colors.grey[100],
                // 根据屏幕宽度切换布局方向
                child: isWideScreen
                    ? const Row(
                        mainAxisAlignment: MainAxisAlignment.spaceAround,
                        children: [Text('宽屏布局'), Text('水平排列')],
                      )
                    : const Column(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [Text('窄屏布局'), Text('垂直排列')],
                      ),
              );
            },
          ),
        ),
      ),
    );
  }
}
注意事项
  • LayoutBuilderbuilder 函数会在父约束变化时重新执行(如屏幕旋转),适合做自适应布局;
  • 不要在 builder 中执行耗时操作,会影响布局性能;
  • constraints 参数是父组件传递的约束,而非当前组件的最终尺寸。

四、综合应用案例

需求场景

实现一个「自适应屏幕的商品卡片」:

  1. 卡片有圆角边框、内边距、外边距;
  2. 宽屏(>600)时水平排列(图片+信息),窄屏时垂直排列;
  3. 信息区域弹性分配剩余空间,价格文字占比更大;
  4. 卡片尺寸受父约束限制(最大宽度500,最小高度200)。

注意点:先通过BoxConstraints定尺寸边界,再用Padding/Margin/Border做装饰,最后结合Flex/Expanded/LayoutBuilder实现弹性+自适应布局。

完整代码

php 复制代码
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.blue),
      home: Scaffold(
        appBar: AppBar(title: const Text('综合布局装饰案例')),
        body: Center(
          // 父容器:给卡片设置最大宽度约束
          child: Container(
            constraints: const BoxConstraints(maxWidth: 500),
            padding: const EdgeInsets.all(15), // 页面内边距
            child: _buildProductCard(),
          ),
        ),
      ),
    );
  }

  // 商品卡片核心组件
  Widget _buildProductCard() {
    return LayoutBuilder(
      builder: (context, constraints) {
        // 1. 获取父约束,判断屏幕/容器宽度
        bool isWide = constraints.maxWidth > 600;
        double imageSize = isWide ? 180 : double.infinity; // 宽屏图片固定宽,窄屏占满

        // 2. 卡片容器:装饰(边框、圆角)+ 外边距 + 最小高度约束
        return Container(
          margin: const EdgeInsets.only(bottom: 10), // 卡片外边距
          constraints: const BoxConstraints(minHeight: 200), // 紧约束-最小高度
          decoration: BoxDecoration(
            border: Border.all(width: 1, color: Colors.grey[300]!),
            borderRadius: BorderRadius.circular(12), // 圆角边框
            boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
          ),
          // 3. 弹性布局:宽屏Row,窄屏Column
          child: isWide ? Row(children: _buildCardChildren(imageSize)) : Column(children: _buildCardChildren(imageSize)),
        );
      },
    );
  }

  // 卡片子组件(图片+信息区域)
  List<Widget> _buildCardChildren(double imageSize) {
    return [
      // 商品图片:内边距 + 固定尺寸
      Padding(
        padding: const EdgeInsets.all(10), // 图片内边距
        child: Container(
          width: imageSize,
          height: 180,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(8),
            image: const DecorationImage(
              image: NetworkImage('https://picsum.photos/200/200'),
              fit: BoxFit.cover,
            ),
          ),
        ),
      ),
      // 信息区域:弹性分配剩余空间
      Expanded(
        child: Padding(
          padding: const EdgeInsets.all(15), // 信息区域内边距
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Text(
                'Flutter 进阶布局实战商品',
                style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              const Text('精细化UI布局 + 自适应屏幕', style: TextStyle(color: Colors.grey)),
              const SizedBox(height: 12),
              // 价格区域:弹性占比
              Row(
                children: [
                  Expanded(
                    flex: 2, // 价格占2份
                    child: Text(
                      '¥99.00',
                      style: TextStyle(fontSize: 20, color: Colors.red[600], fontWeight: FontWeight.bold),
                    ),
                  ),
                  Expanded(
                    flex: 1, // 销量占1份
                    child: Text(
                      '销量 1000+',
                      style: TextStyle(fontSize: 14, color: Colors.grey[600]),
                      textAlign: TextAlign.right,
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    ];
  }
}

效果说明

  • 当父容器宽度>600时:卡片水平排列,图片固定180宽度,信息区域占剩余空间,价格文字占比2/3,销量占1/3;
  • 当父容器宽度≤600时:卡片垂直排列,图片占满宽度,信息区域垂直展示;
  • 卡片整体有圆角边框、阴影、内外边距,尺寸受约束限制(最大宽度500,最小高度200),适配不同屏幕。
相关推荐
weixin_443478512 小时前
flutter学习之状态管理相关组件
javascript·学习·flutter
键盘鼓手苏苏3 小时前
Flutter 组件 reaxdb_dart 适配鸿蒙 HarmonyOS 实战:响应式 NoSQL 数据库,构建高性能本地持久化与分布式状态同步架构
flutter·harmonyos·鸿蒙·openharmony·reaxdb_dart
亚历克斯神3 小时前
Flutter for OpenHarmony: Flutter 三方库 mongo_dart 助力鸿蒙应用直连 NoSQL 数据库构建高效的数据流转系统(纯 Dart 驱动方案)
android·数据库·flutter·华为·nosql·harmonyos
加农炮手Jinx3 小时前
Flutter for OpenHarmony:postgres 直连 PostgreSQL 数据库,实现 Dart 原生的高效读写(数据库驱动) 深度解析与鸿蒙适配指南
网络·数据库·flutter·华为·postgresql·harmonyos·鸿蒙
始持18 小时前
第二讲 Flutter 文字、图片与图标(基础视觉元素)
flutter
Trust yourself2431 天前
Flutter开发中遇到下载Flutter SDK速度缓慢问题
flutter
始持1 天前
第一讲 Flutter核心思想与基础布局
flutter
Trust yourself2431 天前
Flutter增量编译
flutter