阅读视图

发现新文章,点击刷新页面。

Flutter ——流式布局(Wrap)

Flutter 中 Wrap组件是解决 Row/Column 溢出问题的另一种重要方案,下面从核心作用、基础用法、核心属性、实战场景和对比 Row 这几个方面,给你做全面且易懂的讲解。

一、Wrap 核心作用

Wrap 是流式布局组件,和 Row/Column 最大的区别是:

  • Row/Column 子组件总尺寸超过父容器时会溢出(出现警告);
  • Wrap 子组件总尺寸超过父容器时会自动换行 / 换列,不会溢出。

简单说:Wrap 就是 “可以自动换行的 Row” 或 “可以自动换列的 Column”。

二、基础用法

先看一个最基础的示例,直观感受 Wrap 的效果:

lass MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("哈哈"),
        ),
        body: Padding(padding: EdgeInsetsGeometry.all(10),
          child: Wrap(
            children: [
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('1')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('2')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('3')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('4')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('5')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('6')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('7')),
              ),
              Container(width: 80,height: 80,
                margin: const EdgeInsets.all(5),
                color: Colors.red,
                child: Center(child: Text('8')),
              )
            ],
          ),
        ),
      ),
    );
  }
}
图片.png

效果:8 个 80x80 的容器会先在第一行排列,当剩余宽度不够放下下一个容器时,自动换行到第二行,完全适配父容器宽度,无溢出。

三、核心属性详解

Wrap 的属性和 Row/Column 高度相似,但新增了换行相关的属性:

属性 作用 常用值
direction 排列方向(主轴) Axis.horizontal(默认,水平)/ Axis.vertical(垂直)
alignment 主轴方向的对齐方式(单行 / 列的对齐) WrapAlignment.start(默认)/ center/ end/ spaceBetween/ spaceAround/ spaceEvenly
crossAxisAlignment 交叉轴方向的对齐方式(行 / 列之间的对齐) WrapCrossAlignment.start(默认)/ center/ end
runAlignment 多行 / 多列整体的对齐方式 WrapAlignment.start(默认)/ center/ end/ 等
spacing 主轴方向子组件之间的间距 数值(如 8.0)
runSpacing 交叉轴方向(行 / 列之间)的间距 数值(如 8.0)
children 子组件列表 Widget 数组

关键属性实战示例

Wrap(
  direction: Axis.horizontal, // 水平排列
  alignment: WrapAlignment.spaceBetween, // 单行内两端对齐
  runAlignment: WrapAlignment.center, // 多行整体居中
  crossAxisAlignment: WrapCrossAlignment.center, // 行内垂直居中
  spacing: 10, // 水平子组件间距 10
  runSpacing: 15, // 行与行之间的间距 15
  children: [
    Container(width: 70, height: 70, color: Colors.red),
    Container(width: 70, height: 80, color: Colors.green),
    Container(width: 70, height: 70, color: Colors.blue),
    Container(width: 70, height: 70, color: Colors.yellow),
    Container(width: 70, height: 70, color: Colors.purple),
    Container(width: 70, height: 70, color: Colors.yellow),
    Container(width: 70, height: 70, color: Colors.purple),
  ],
)
图片.png

四、常见使用场景

Wrap 是 Flutter 中实现 “标签流、按钮流、网格标签” 的首选组件,以下是两个高频实战场景:

场景 1:标签列表(最经典用法)

dart

// 模拟动态标签列表
Wrap(
  spacing: 8, // 标签之间的水平间距
  runSpacing: 8, // 行之间的垂直间距
  children: [
    // 标签组件封装
    _buildTag('Flutter'),
    _buildTag('Dart'),
    _buildTag('Android'),
    _buildTag('iOS'),
    _buildTag('前端'),
    _buildTag('移动端'),
    _buildTag('跨平台'),
    _buildTag('布局'),
    _buildTag('组件'),
  ],
)

// 封装标签 Widget
Widget _buildTag(String text) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
    decoration: BoxDecoration(
      color: Colors.grey[200],
      borderRadius: BorderRadius.circular(20),
    ),
    child: Text(text),
  );
}

五、Wrap vs Row/Column 核心对比

特性 Wrap Row/Column
溢出处理 自动换行 / 换列,无溢出 直接溢出,出现警告
空间占用 仅包裹子组件(mainAxisSize 固定为 min) 可设置 max/min,默认 max
适用场景 动态数量的子组件(标签、按钮) 固定数量的子组件(导航栏、表单行)
性能 略优(无需计算溢出) 需计算主轴空间,溢出时性能无影响

六、常见问题与注意事项

  1. Wrap 中使用 Expanded 无效:Expanded 是配合 Flex(Row/Column)的弹性布局组件,Wrap 不支持弹性分配空间,因此在 Wrap 的 children 中用 Expanded 不会有任何效果。

  2. 控制 Wrap 整体的宽度 / 高度:如果想让 Wrap 占满父容器宽度(而非仅包裹子组件),可以给 Wrap 包裹一个 Container 并设置宽度:

    dart

    Container(
      width: double.infinity, // 占满父容器宽度
      child: Wrap(/* ... */),
    )
    

总结

  1. 核心定位:Wrap 是流式布局,解决 Row/Column 溢出问题,子组件超出父容器时自动换行 / 换列。

  2. 核心属性spacing(子组件间距)、runSpacing(行 / 列间距)、direction(排列方向)是最常用的三个属性。

  3. 使用技巧

    • 动态数量的标签、按钮优先用 Wrap;
    • Wrap 不支持 Expanded,无需尝试弹性分配空间;
    • 控制间距优先用 spacing/runSpacing,而非子组件的 margin(更统一)。

掌握 Wrap 布局,就能轻松实现 Flutter 中绝大多数 “流式排列” 的 UI 场景,是替代 Row/Column 解决溢出问题的最佳选择。

Flutter——弹性布局(Flex、Expanded)

弹性布局(Flex)允许子组件按照一定比例来分配父容器空间。

一、核心关系与基础概念

首先要明确一个关键关系:Row 和 Column 都是 继承自Flex 组件的 ——

  • Row = Flex(direction: Axis.horizontal)(水平弹性布局)
  • Column = Flex(direction: Axis.vertical)(垂直弹性布局)

Expanded 是配合 Flex(包括 Row/Column)使用的 “空间分配器”,用于控制子组件在弹性布局中占据的空间比例。

1. Flex 组件的基础用法

Flex 是弹性布局的基类,直接使用的场景较少(优先用 Row/Column),但理解它能帮你掌握弹性布局的本质:

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Flex & Expanded 详解')),
        body: Padding(
          padding: const EdgeInsets.all(20),
          // 基础 Flex 布局(水平方向,等价于 Row)
          child: Flex(
            direction: Axis.horizontal, // 布局方向:horizontal(水平)/vertical(垂直)
            children: [
              Container(width: 80, height: 80, color: Colors.red),
              Container(width: 80, height: 80, color: Colors.green),
              Container(width: 80, height: 80, color: Colors.blue),
            ],
          ),
        ),
      ),
    );
  }
}

二、Flex 核心属性(Row/Column 通用)

Flex 的核心属性和 Row/Column 完全一致,这里重点强调弹性布局相关的关键属性:

属性 作用 常用值
direction 布局方向(Flex 独有,Row/Column 已固定) Axis.horizontal / Axis.vertical
mainAxisAlignment 主轴对齐方式 start/center/end/spaceBetween/spaceAround/spaceEvenly
crossAxisAlignment 交叉轴对齐方式 start/center/end/stretch
mainAxisSize 主轴占用空间 max(默认,占满父容器)/ min(仅包裹子组件)
children 子组件列表 Widget 数组

三、Expanded 组件详解

Expanded 是 Flex 布局的 “黄金搭档” ,用于解决布局溢出问题,并按比例分配剩余空间。

1. 核心作用

  • 强制子组件占据 Flex 布局中剩余的全部 / 部分空间
  • 自动适配父容器尺寸,避免子组件溢出(最核心的用途)
  • 支持通过 flex 属性设置空间分配比例

2. 基础属性

属性 作用 默认值
flex 空间分配权重(比例) 1
child 需要分配空间的子组件 必填

3. 实战示例

示例 1:基础比例分配(解决溢出)
// Flex(Row)+ Expanded 按比例分配空间
Row(
  children: [
    // 占1份空间
    Expanded(
      flex: 1,
      child: Container(color: Colors.red, height: 60),
    ),
    // 占2份空间
    Expanded(
      flex: 2,
      child: Container(color: Colors.green, height: 60),
    ),
    // 占1份空间
    Expanded(
      flex: 1,
      child: Container(color: Colors.blue, height: 60),
    ),
  ],
)

效果:父容器宽度被分成 1+2+1=4 份,红色占 1/4,绿色占 2/4,蓝色占 1/4,完全适配父容器,无溢出。

示例 2:混合固定尺寸 + 弹性尺寸
// 固定尺寸 + Expanded 弹性尺寸
Row(
  children: [
    // 固定宽度的按钮
    const SizedBox(width: 80, child: ElevatedButton(onPressed: () {}, child: Text('返回'))),
    // 占满剩余所有空间的文本(flex 默认1)
    Expanded(
      child: Container(
        color: Colors.grey[200],
        alignment: Alignment.center,
        child: const Text('这是标题,会占满剩余空间'),
      ),
    ),
    // 固定宽度的图标
    const SizedBox(width: 80, child: Icon(Icons.more_vert)),
  ],
)

效果:左右两个固定宽度的组件,中间文本区域自动占满剩余所有宽度,适配不同屏幕尺寸。

示例 3:垂直方向(Column + Expanded)
// Column + Expanded 垂直方向分配空间
Column(
  children: [
    // 顶部固定高度
    Container(height: 80, color: Colors.red, child: const Center(child: Text('顶部'))),
    // 中间占满剩余空间(核心内容区)
    Expanded(
      child: Container(color: Colors.green, child: const Center(child: Text('核心内容区'))),
    ),
    // 底部固定高度
    Container(height: 80, color: Colors.blue, child: const Center(child: Text('底部'))),
  ],
)

效果:顶部和底部固定高度,中间内容区自动占满屏幕剩余高度,是 App 页面的经典布局方式。

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("哈哈"),
        ),
        body: Column(
          children: [Flex(direction: .horizontal,
            children: [
              Expanded(
                  flex: 1,
                  // 此处设置的宽度 100无效,flex 比 width 优先级高
                  child: Container(width: 100, height: 40,color: Colors.red,)),
              Expanded(
                  flex: 2,
                  child: Container(width: 100,height: 40,color: Colors.yellow,))
            ],
          )],
        ),
      ),
    );
  }
}

Expandedflex 优先级远高于子组件的 widthExpanded 的作用是强制子组件占据 Flex 布局中分配给它的空间,因此你给 Container 设置的 width: 100 会被完全忽略;

Flutter——线性布局(Row、Column)

Flutter 中最基础也最核心的 Row(行)和 Column(列)布局组件,我会从基础用法、核心属性、常见问题和实战示例几个方面,给你做一个通俗易懂的全面讲解。

一、核心概念与基础用法

Row 和 Column 是 Flutter 中用于线性布局的核心组件:

  • Row:在水平方向上排列子组件(从左到右,可通过属性调整)
  • Column:在垂直方向上排列子组件(从上到下,可通过属性调整)

它们都继承自 Flex 组件,核心逻辑一致,只是排列方向不同。

1. 最基础的使用示例

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Row & Column 详解')),
        body: Padding(
          padding: const EdgeInsets.all(20),
          // 外层 Column 垂直排列
          child: Column(
            // 子组件
            children: [
              // 第一个子组件:Row 水平排列
              Row(
                children: [
                  Container(width: 80, height: 80, color: Colors.red),
                  Container(width: 80, height: 80, color: Colors.green),
                  Container(width: 80, height: 80, color: Colors.blue),
                ],
              ),
              const SizedBox(height: 20), // 间距
              // 第二个子组件:Column 垂直排列
              Column(
                children: [
                  Container(width: 80, height: 80, color: Colors.yellow),
                  Container(width: 80, height: 80, color: Colors.purple),
                  Container(width: 80, height: 80, color: Colors.orange),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

二、核心属性详解(Row/Column 通用)

这是掌握布局的关键,重点属性如下:

属性 作用 常用值
mainAxisAlignment 主轴对齐方式(Row 主轴 = 水平,Column 主轴 = 垂直) start(默认)、centerendspaceBetweenspaceAroundspaceEvenly
crossAxisAlignment 交叉轴对齐方式(Row 交叉轴 = 垂直,Column 交叉轴 = 水平) startcenter(默认)、endstretch(拉伸填满)
mainAxisSize 主轴占用空间大小 max(默认,占满父组件)、min(仅包裹子组件)
children 子组件列表 任意 Widget 数组

关键属性实战示例

// 演示 mainAxisAlignment 和 crossAxisAlignment 的效果
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween, // 水平两端对齐,中间均匀间距
  crossAxisAlignment: CrossAxisAlignment.center,    // 垂直居中
  mainAxisSize: MainAxisSize.max,                   // 占满父组件宽度
  children: [
    Container(width: 50, height: 50, color: Colors.red),
    Container(width: 50, height: 80, color: Colors.green), // 高度不同,看交叉轴对齐
    Container(width: 50, height: 50, color: Colors.blue),
  ],
)

三、常见问题与解决方案

1. Row/Column 溢出(Overflow)

问题:子组件总宽度 / 高度超过父组件,出现黄色 / 黑色溢出警告。解决方案

  • 使用 Expanded:让子组件占满剩余空间(可设置 flex 比例)
  • 使用 Flexible:类似 Expanded,但不会强制填满(可设置 fit 属性)
  • 使用 SingleChildScrollView:可滚动的布局(横向 / 纵向)
// Expanded 解决溢出问题(按比例分配空间)
Row(
  children: [
    Expanded(flex: 1, child: Container(color: Colors.red, height: 50)), // 1份
    Expanded(flex: 2, child: Container(color: Colors.green, height: 50)), // 2份
    Expanded(flex: 1, child: Container(color: Colors.blue, height: 50)), // 1份
  ],
)

// SingleChildScrollView 解决溢出(可滚动)
SingleChildScrollView(
  scrollDirection: Axis.horizontal, // 横向滚动
  child: Row(
    children: [
      for (int i = 0; i < 10; i++) Container(width: 80, height: 80, color: Colors.primaries[i % 10]),
    ],
  ),
)

2. Column 嵌套 Column 高度异常

问题:内层 Column 占满外层 Column 高度,导致布局错乱。解决方案:给内层 Column 包裹 mainAxisSize: MainAxisSize.min

Column(
  children: [
    Text('标题'),
    // 内层 Column 仅包裹子组件,不占满高度
    Column(
      mainAxisSize: MainAxisSize.min,
      children: [Text('内容1'), Text('内容2'), Text('内容3')],
    ),
  ],
)

四、实战综合示例

// 模拟一个常见的页面布局
Scaffold(
  body: Column(
    children: [
      // 顶部导航栏区域(Row)
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          IconButton(icon: const Icon(Icons.arrow_back), onPressed: () {}),
          const Text('详情页', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
          IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}),
        ],
      ),
      // 分割线
      const Divider(height: 1),
      // 内容区域(占满剩余高度)
      Expanded(
        child: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              // 图片
              Image.network('https://picsum.photos/400/200'),
              // 文本区域
              Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start, // 文本左对齐
                  children: const [
                    Text('商品名称', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
                    SizedBox(height: 8),
                    Text('价格:¥99.00', style: TextStyle(color: Colors.red, fontSize: 18)),
                    SizedBox(height: 8),
                    Text('商品描述:这是一个测试商品,用于演示 Row 和 Column 布局'),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
      // 底部按钮区域(Row)
      Row(
        children: [
          Expanded(child: ElevatedButton(onPressed: () {}, child: const Text('加入购物车'))),
          Expanded(child: ElevatedButton(onPressed: () {}, style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('立即购买'))),
        ],
      ),
    ],
  ),
)

总结

  1. 核心定位:Row 是水平线性布局,Column 是垂直线性布局,核心属性(mainAxisAlignment/crossAxisAlignment)控制对齐方式。
  2. 溢出解决:溢出是最常见问题,优先用 Expanded(按比例分配)或 SingleChildScrollView(可滚动)解决。
  3. 关键技巧:嵌套布局时,记得给内层 Column/Row 设置 mainAxisSize: MainAxisSize.min,避免不必要的空间占用。

iOS——IPATool工具的使用

IPATool 是一款命令行工具,可通过 Apple ID 从 App Store 下载加密 IPA 包,支持多平台(macOS/Windows/Linux),适用于开发者测试、版本归档等场景。

一、安装(分平台)

1. macOS(推荐 Homebrew)

# 安装 ipatool
brew install ipatool
# 验证
ipatool --version
// 结果 ipatool version 2.1.6
  1. 验证:终端输入 ipatool --version 显示版本号即可。

二、核心流程:认证 → 搜索 → 下载

1. 账号认证(必需)

bash

运行

# 登录 Apple ID(开启双重验证需输入验证码)
ipatool auth login -e 你的邮箱 -p 你的密码
# 查看登录信息
ipatool auth info
# 登出/撤销凭证
ipatool auth revoke

注意:双重验证环境下,密码需用「App 专用密码」(Apple ID 管理页生成),避免登录失败。

2. 搜索应用(获取 Bundle ID/App ID)

# 搜索关键词,限制返回 5 条结果
ipatool search "微信" --limit 5
# 输出示例(含 Bundle ID:com.tencent.xin)

3. IPA文件下载

找到目标应用后,使用应用ID进行下载:

ipatool download --app-id 应用ID --output 保存路径
//例 ipatool download --app-id 155342910943 --output 保存路径

备注: 下载提示「未购买」未加 --purchase 参数首次下载添加 --purchase 获取许可

❌