多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Flutter Container完全指南:源码拆解与实战避坑

Flutter Container完全指南:源码拆解与实战避坑 Container 可以说是 Flutter 组件库里的“瑞士军刀”。每天写 Flutter 的人几乎每个页面都会碰到它但很多人对 Container 的理解就停留在“一个能调背景色、能调圆角的方块”。其实 Container 并不是一个底层渲染组件它更像是官方封装好的一套组合工具对齐、内边距、外边距、背景、边框、圆角、阴影、渐变、尺寸约束、变换全都塞进了一个组件里。你随便用用能解决 80% 的布局需求但如果你弄懂了它的组合顺序和约束逻辑你才真正摸到了 Flutter 布局哲学的门把手。这篇指南会从组件定位讲起逐个拆属性、讲源码、跑实战、排坑最后附带面试考点适合刚入门还没吃透布局的新手也适合已经写了一阵子 Flutter 但没仔细看过 Container 源码的开发者。顺便说一句搜 Container 时一大半结果是 Docker 容器、群晖 Container Manager、NVIDIA Container Toolkit 这些东西和 Flutter UI 里的 Container 完全是两码事走错片场的同学请自觉回到本篇。1. 先弄懂Container的定位它到底是什么1.1 从Flutter的Widget体系说起Flutter 所有界面都是 Widget而 Widget 本身是不可变的配置描述。一次界面绘制会经过三层协作Widget 描述 UI 长什么样、Element 维护实例关系、RenderObject 负责实际布局与绘制。大多数 UI 组件都很“专一”Text 管文字、Image 管图片、Padding 管内边距。而 Container 走的是另一条路它是一个 StatelessWidget自己并不直接绘制或布局而是把若干个单职责组件按固定顺序组合起来再整体抛给你。这就意味着Container 的每个行为都能拆解成内部真实组件的行为。你给 Container 设置 alignment其实它内部实例化了一个 Align你设置 padding内部就多一个 Padding你给 decoration内部就有一个 DecoratedBox。Container 本质上是一个“组件的组件”理解这一点是学好它的前提。用工厂做类比更直观。Container 是个组装车间传进来的 child 是毛坯件。车间内部工序依次是先按需产生一个占位底板当没有 child 时再做对齐再塞内边距再刷背景再画前景装饰最后加约束和变换。每一步工序干的事恰好对应一个独立组件。所以Container 的显示效果是整套工序叠加后的结果而不是某个单独属性决定的。1.2 Container源码拆解一个组合组件为了把原理说透这里放一段精简后的源码基于 Flutter 3.x 的 build 方法去掉了非核心分支override Widget build(BuildContext context) { Widget? current child; if (child null (constraints null || !constraints!.isTight)) { current LimitedBox( maxWidth: 0.0, maxHeight: 0.0, child: ConstrainedBox(constraints: const BoxConstraints.expand()), ); } if (alignment ! null) { current Align(alignment: alignment!, child: current); } final EdgeInsetsGeometry? effectivePadding _paddingIncludingDecoration; if (effectivePadding ! null) { current Padding(padding: effectivePadding, child: current); } if (decoration ! null) { current DecoratedBox(decoration: decoration!, child: current); } if (foregroundDecoration ! null) { current DecoratedBox( decoration: foregroundDecoration!, position: DecorationPosition.foreground, child: current, ); } if (constraints ! null) { current ConstrainedBox(constraints: constraints!, child: current); } if (transform ! null) { current Transform(transform: transform!, alignment: transformAlignment, child: current); } return current!; }这套流程对应几个重要现象第一Container 没有 child 时会通过 LimitedBox 和 ConstrainedBox 生成一个“空但尽量大”的底板所以空 Container 在宽松约束下会撑满可用区域这让很多新手困惑“为什么空 Container 这么大”第二alignment 出现在靠内的位置说明它作用在 child 上不会影响背景区域的对齐关系第三padding 处理时用的是_paddingIncludingDecoration会把 decoration 的边框宽度也算进去避免 child 被装饰边框遮住第四你单独传 color 时构造函数内部会转成BoxDecoration(color: color)所以别再同时传 color 和 decorationFlutter 在 debug 模式下会抛断言。还有一个容易忽略的参数是 clipBehavior。新版 Container 在设置 decoration 后可以配合裁剪比如圆角背景里的 child 溢出了设置Clip.antiAlias就能按圆角边界裁剪。老代码里很多人习惯包一层 ClipRRect现在直接用 Container 的 clipBehavior 就能少包一层代码更干净。1.3 什么时候该用它什么时候别用Container 方便归方便但不要事事都用。如果只是需要固定宽高用 SizedBox只需要空白间距用 Padding只要纯色背景用 ColoredBox。这些单职责组件对应的构建链更短、RenderObject 更单一。从语法糖的角度看Container 只是把常见的小组件打包了并没有提供“独一无二的渲染能力”所以在追求极致性能的页面上可以用更瘦的组件替换它。实际开发里性能差距通常可以忽略尤其是现代 Flutter 的 Widget 重建机制下多创建几个 Widget 配置对象并不昂贵。真正值得警惕的是 Container 内部携带的绘制开销大半径阴影、模糊、裁剪、复杂渐变这些在 GPU 阶段会累积。所以我个人的习惯是一次只需要一项能力时用专一组件需要同时处理布局和装饰时才放心上 Container。代码的可读性和维护性永远比“少创建几个组件”更重要这个排序不要搞反。2. 核心属性逐个拆解位置、尺寸、装饰一次讲透2.1 alignment、padding、margin控制位置与间距alignment 是 Container 最常用的隐藏功能之一。给 Container 设置 alignment 后内部会实例化一个 Align 组件把 child 在可用区域内对齐。这里有个很多人不知道的细节当 Container 没有 child、只设置了 alignment 时它会在父级约束允许的范围内尽可能展开把可用区域撑满。这个行为就来自 build 方法里的 LimitedBox 和 ConstrainedBox 默认占位逻辑。Container( alignment: Alignment.centerRight, height: 80, color: Colors.blueGrey.withAlpha(30), child: const Text(右对齐), )一段代码就实现了“撑满宽度、高度 80、子内容靠右”的效果。如果换成 Stack Align 组合写代码量会明显增加而且可读性更差。alignment 常用的取值除了Alignment.center、centerLeft、centerRight、topCenter、bottomCenter这些还可以用Alignment(x, y)传入 -1 到 1 之间的浮点数做更精细的偏移。padding 和 margin 的区别一句话就能说清padding 是内边距会把 child 往里挤同时参与背景的绘制区域——因为 decoration 在 padding 外层背景会覆盖 padding 区域margin 是外边距位于最外层不参与背景绘制。实际项目中Container(margin: EdgeInsets.all(16), padding: EdgeInsets.all(12))这种“外层留白、内层不贴边”的组合非常常见视觉层次一步到位。再补一个细节EdgeInsets 区分普通版和 Directional 版中文环境下普通版够用做国际化适配时建议用EdgeInsetsDirectional这样在 RTL 语言环境下左右内边距会自动镜像。2.2 decoration与foregroundDecoration圆角、阴影、渐变、背景图这是 Container 价值密度最高的部分。decoration 的类型是 Decoration最常用的实现是 BoxDecoration。它把背景色、渐变、边框、圆角、阴影全部收纳到一个属性里比分开设置省事得多。Container( width: 120, height: 120, decoration: BoxDecoration( gradient: LinearGradient( colors: [const Color(0xFF667EEA), const Color(0xFF764BA2)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: const Color(0x33764BA2), blurRadius: 12, offset: const Offset(0, 6), ), ], ), child: const Center(child: Text(渐变卡片)), )圆角的坑要提前说BoxDecoration 同时设置 borderRadius 和shape: BoxShape.circle时borderRadius 会失效因为 circle 自带形状。另一个高频报错是“设置了 decoration 的情况下还直接用 color”Flutter 会在 debug 模式下直接断言失败解决办法是把 color 挪进 BoxDecoration 里。foregroundDecoration 很多人没用过它是绘制在 child 上方的装饰层。常见场景是做遮罩底层 Container 放图片foregroundDecoration 放一层半透明黑色渐变文字压在图片上依然清晰。这个方案能替代“Stack Positioned 两层 Container”的写法结构上更简洁。另外记住阴影默认不占布局尺寸当 blurRadius 比较大时阴影可能被父级裁剪需要阴影完整显示时在外层套一个 Padding 或直接给父级留边距。2.3 constraints、transform、width/height尺寸与变换的关键Container 的 width、height 其实只是语法糖内部会被转化成 BoxConstraints 的 tight 约束。也就是说Container(width: 100)实际生成的是 minWidth 100、maxWidth 100 这样一组“紧约束”。理解了这一点你就能明白为什么有时设置了宽高却不生效如果父级传入的是更窄的紧约束子 Container 的约束会被“夹紧”最终以父级为准。transform 属性用来做旋转、缩放、平移内部包装的是 Transform 组件。和布局里直接修改尺寸不同transform 只改变绘制结果不改变占位尺寸。比如做一个点击缩放效果可以传Matrix4.diagonal3Values(0.9, 0.9, 1)。需要注意使用 transform 后点击区域仍然按照未变换的布局区域判断如果你期望视觉缩放和点击区域一致需要额外处理命中测试这个细节在自定义交互动效时经常被忽略。最后聊 constraints。不要在同一行代码里同时写 constraints 和 width/height容易出问题。比如Container(constraints: BoxConstraints(minWidth: 50), width: 100)实际约束是两个约束合并后的结果虽然大多数情况下能按预期工作但心智负担很大。正确的姿势是需要“最小宽度”“最大宽度”这种弹性约束时用 constraints需要固定大小时用 width/height需要两者结合时宁可拆成两行组件嵌套也不要挤在一个 Container 里试错。3. 实战用Container搭出日常90%的界面3.1 做一个漂亮的卡片组件卡片是移动端最常用的布局容器。用 Container 搭卡片核心就是“背景 圆角 阴影 内边距”的组合。一个带标题和副标题的卡片可以这样写Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade200), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(10), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(标题, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 8), Text( 这是说明文字内容比较长的时候会自动换行Container 会根据约束自动调整高度。, style: Theme.of(context).textTheme.bodyMedium, ), ], ), )这里有个实践经验Material 库里的 Card 组件自带 Material 图层和默认边距自定义样式时往往要额外 override反而不如 Container 一套 BoxDecoration 来得干净。什么时候选 Card需要水波纹点击效果、需要 Material 语义化的场景用 Card 更省事如果只是展示型卡片、设计稿又有特殊圆角边框Container 更顺手。3.2 渐变按钮与半透明遮罩背景图业务里做个性化按钮很多人会在 Button 的 style 里调半天。其实用 Container 更直接外层包 GestureDetector 处理点击Container 负责视觉品牌色定制非常方便。GestureDetector( onTap: () {}, child: Container( width: double.infinity, height: 48, alignment: Alignment.center, decoration: BoxDecoration( gradient: LinearGradient( colors: [const Color(0xFF7B2FF7), const Color(0xFF2F80ED)], begin: Alignment.centerLeft, end: Alignment.centerRight, ), borderRadius: BorderRadius.circular(24), ), child: const Text(登录, style: TextStyle(color: Colors.white, fontSize: 16)), ), )背景图加遮罩也是高频需求。Container 的 decoration.image 可以直接放图片配合 foregroundDecoration 实现“图片上方叠加半透明遮罩”不用再嵌套 Stack。Container( width: double.infinity, height: 200, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(assets/banner.png), fit: BoxFit.cover, ), ), foregroundDecoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.transparent, Colors.black54], ), ), child: const Align( alignment: Alignment.bottomLeft, child: Padding( padding: EdgeInsets.all(16), child: Text(底部文字, style: TextStyle(color: Colors.white, fontSize: 18)), ), ), )这个模式在 Banner、列表头图、直播封面里非常常见代码结构比“Stack 两层 Container”清晰不少。至于要跳转原生 Activity 的场景Container 本身管不到那是原生导航通道的事别把两层问题混在一起。3.3 列表项与分割线Container在ListView里的正确姿势列表页里最常见的写法是每个 item 包一层 Container。列表项的关键是用 margin 控制 item 间距、用 padding 控制内边距、用底部边框模拟分割线。ListView.separated( itemCount: items.length, separatorBuilder: (context, index) const Divider(height: 1), itemBuilder: (context, index) Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), color: Colors.white, child: Text(items[index]), ), )很多人问为什么我的 Container 在 ListView 里设置 height 不生效因为 ListView 给每个 item 的宽度是紧约束高度却是松约束。如果 Container 指定 height: 60而 child 内容实际高度超过 60Container 会优先适配 child 高度。解决方式是把 child 塞进一个约束组件里或者在 Container 里加 alignment 并锁定高度让 Align 在整体高度内对齐。还要补一个冷知识大列表里不要给每个 item 都套一层带大阴影的 Container。ListView 会缓存渲染对象但圆角加阴影的绘制开销在大列表下会被放大。需要列表项有明确分组感时优先用边框或浅色底色代替阴影实测对滚动流畅度有帮助。3.4 自适应布局百分比与动态尺寸Container 没有直接的百分比宽高属性这是很多新手会困惑的点。在 Flutter 里做百分比布局一般有三种方式FractionallySizedBox、LayoutBuilder、MediaQuery。Container 主要是和它们配合而不是替代它们。SizedBox( width: double.infinity, height: 200, child: FractionallySizedBox( widthFactor: 0.5, child: Container(color: Colors.amber), ), )这段代码实现“占父级一半宽度”。为什么不用 Container 自带的 width 写因为 Container 的 width 必须是一个明确的 double不能写“0.5 倍”。FractionallySizedBox 把宽度因子交给父级计算才能真正响应式变化。另外如果父级是 Row 或 ColumnContainer 的宽度还受主轴分配约束的影响比如 Row 里包了 Flexible 或 ExpandedContainer 的 width 可能会被外力改变。另一个常用场景是“内容自动撑开但最大不超过某个值”这就是 Container 的 constraints 甜点区Container( constraints: const BoxConstraints(maxWidth: 300), padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: Text(内容很长时会自动换行但整个气泡不会超过 maxWidth 的限制), )这种气泡式布局在聊天界面、标签云、Toast 上非常常见。Constraints 的弹性语义比 width 更贴近业务需求建议记在脑子里。4. 原理与性能搞懂Container会不会拖慢页面4.1 组合组件与构建开销const是最容易忽略的优化回到源码Container 每次 build 都会创建多个组件实例。但 Flutter 的 Widget 是不可变配置对象重建成本极低Element 会复用旧实例。所以真正需要关心的不是“Container 创建了几个组件”而是“同一个 build 方法里被重复创建了多少个非必要的 Container”。最容易被忽略的优化是 const。一个const Container可以在编译期被缓存运行时几乎零成本非 const 的 Container 即使外观完全一样每次父级 rebuild 都会重新创建实例。你会发现很多项目里 build 方法写了十几层非 const Container虽然不至于卡但代码审查时这属于可以快速提升的点。另一个性能点是层级过深。Container 本身已经是几层组件的组合如果你再套十层 Container渲染树里就会多出几十个 RenderObject。Flutter 的布局遍历对深度没有特殊优化深度增加会拖慢布局计算。能用一行代码解决的对齐不要套三个 Container。4.2 渲染侧那些事Skia、Impeller与阴影圆角讲完 Widget 层的取舍再看渲染层。早期 Flutter 使用 Skia 渲染运行时需要编译着色器第一次滚动或打开新页面时偶发卡顿。近几个版本逐步切换到 Impeller预编译所有着色器圆角、阴影这类图形的绘制开销更稳定。而这正好和 Container 的 decoration 强相关Container 的圆角、阴影、渐变最终都会变成绘图指令由渲染管线负责执行。实践建议很直接不要滥用大半径阴影。BoxShadow 的 blurRadius 越大绘制区域越复杂。即使 Impeller 已经做了优化非必要的阴影仍然会占用 GPU 带宽。普通卡片用一个 8 到 12 的模糊半径就够需要立体感时优先用边框或浅色调变化替代阴影。另外如果某个 Container 在页面里只是“装饰”比如一根 1 像素的分割线、一个纯色色块优先考虑SizedBox(height: 1)加ColoredBox而不是 Container。平时无感但高频页面上渲染压力会小一点。4.3 原生平台协同Container与PlatformView、消息通道移动端开发中页面时常需要嵌入地图、WebView、相机预览等原生 View。Flutter 里的方案是 PlatformView。从布局角度看Container 可以作为原生视图的外层载体负责圆角裁剪和周边间距。Android 上要特别注意PlatformView 默认使用虚拟显示或混合合成Container 的圆角如果用来裁剪原生 Surface部分手机上会出现圆角失效这时候需要配合 clipBehavior或者把裁剪任务放到原生层处理。热词里提到的 eventchannel、组件通信顺带说一句Container 只负责画界面不负责数据通联。原生与 Flutter 通信走 MethodChannel 和 EventChannelMethodChannel 适合“一问一答”EventChannel 适合持续事件流比如传感器数据。把 Container 理解成“前台展示层”通道理解成“后台数据管道”分工就清晰了。像 Okta 这类平台插件要适配鸿蒙时涉及的是通道和原生 Module 的适配跟 Container 本身没有耦合Live Activity 这类系统级能力更是需要在原生端构建 UIFlutter 侧只是通过通道发起请求别指望 Container 能直接画出系统组件。5. 高频问题排查实录Container相关与运行期那些坑5.1 Container使用高频问题速查表问题原因解决办法color 和 decoration 同时设置报错构造函数断言禁止两种背景定义同时出现把 color 挪进 BoxDecoration设置了 width/height 但不生效父级传入更紧的约束检查父组件是不是 SizedBox、ConstrainedBox 等必要时用 Expanded 或 Align 调整外层空 Container 没有撑满父级父级约束本身是宽松的给 Container 加 alignment 或 width: double.infinity阴影不显示阴影被父级裁剪外层留边距或关闭父级的 clipBehavior圆角没生效设置了 shape: BoxShape.circleborderRadius 只对 rectangle 生效child 不被圆角裁剪没有设置 clipBehavior给 Container 设置 Clip.antiAlias误把 Docker Container 报错当成 Flutter 问题概念混淆看下报错来源是 docker daemon 还是 Flutter 引擎注意区分最后一行不是玩笑。热词里的“群晖 NAS Container Manager 无法下载镜像”“failed to start docker application container engine”这些本质上都是容器化技术平台的问题和 Flutter 组件没有关系。如果你搜报错时看到大量 Docker 结果先确认自己的问题发生在哪个工具链再决定去查哪边的文档。5.2 构建期与运行期常见报错Gradle、打包、状态丢失Flutter 新版本升级后一个高频报错是You are applying Flutters main Gradle plugin imperatively using the apply script method, which is no longer supported and will fail in the future.这个报错的意思是 Flutter 工具链已经切换到了 Gradle 的 plugins 块管理方式而老项目还在用 apply script 方式拉取 Flutter Gradle 插件。解决办法是在android/app/build.gradle里删掉类似apply from: $flutterRoot/packages/flutter_tools/gradle/flutter.gradle的代码改成plugins { id com.android.application id kotlin-android id dev.flutter.flutter-gradle-plugin }同时在android/settings.gradle里也要配合 Flutter 模板中的 pluginManagement 配置。如果不想手改最简单的方式是用相同 Flutter 版本重新执行flutter create .把 android 目录重新生成一遍。还有一类打包报错是java.lang.AssertionError: java.lang.Exception: could not close index...多见于 Windows 环境通常和文件句柄被占用、Gradle 缓存损坏有关。执行flutter clean、删除android/.gradle目录再重启 IDE 基本能解决如果仍然失败检查杀毒软件是否锁定了构建目录。另外有读者经常问“Flutter navigator 切换页面后会丢失状态吗”。答案是正常情况下不会。Navigator.push 之后原页面仍然在栈中State 对象也还在。如果你发现 Tab 切换或页面返回后状态丢了大概率是页面重建了比如用了不稳定的 Key或者跳过了 AutomaticKeepAliveClientMixin这跟 Container 没有直接关系但排查时容易被混在一起。最后回答一个 Dart 层面的高频问题Future.then的回调确实会被放入微任务队列在当前同步代码执行完之后立刻执行优先级高于事件队列中的 Timer 和 IO 事件。你可以写几行print验证一下执行顺序理解了事件循环很多“执行顺序诡异”的问题都能自己解释。5.3 新手入门环境问题搭建、创建项目、热重载标题既然叫完全指南还是有不少人私信问环境问题这里简短带过。Flutter 环境搭建三步下载 Flutter SDK、配置 PATH 环境变量、运行flutter doctor检查依赖。用 Android Studio 创建项目是最标准的方式File New New Flutter Project选择 Flutter SDK 路径填好项目名后会自动生成模板。注意 Android SDK 和命令行工具要装全用 Android Studio 内置的 SDK Manager 补。创建完成后热重载是 Flutter 的核心体验。保存代码、点击 IDE 里的 hot reload 按钮几秒内就能看到 Container 属性变化。如果热重载后布局没更新先确认代码是否真的保存了再检查是否有 const 导致组件没有重建。首次在 Windows 上跑安卓项目要下载不少 Gradle 依赖网络不稳时会卡在下载阶段建议提前配置国内镜像仓库能省不少时间。6. 面试与对比Container背后的知识延伸6.1 高频面试题与回答思路面试官如果问“Container 是不是一个布局组件”别急着点头。准确的回答是Container 本质是一个 StatelessWidget内部组合了 Align、Padding、DecoratedBox、ConstrainedBox、Transform 这些基础组件同时承担布局和绘制职责。它既不是纯粹的布局组件也不是纯粹的绘制组件而是两者的“夹心层”。另一个经典问题是“color 和 decoration 为什么不能同时设置”。回答思路要提到断言保护和语义冲突color 本质就是简化的 BoxDecoration同时传两个背景定义会造成歧义所以框架在 debug 模式下直接拒绝。“Container 的宽高为什么有时不生效”这个考点考察的是对 BoxConstraints 的理解。父级传入的如果是紧约束子组件设置的约束会被 enforce 逻辑夹紧最终以父级为准。能举出 ListView 和 Row 里的例子基本就稳了。还有个常见对比题Container 和 SizedBox、Padding、ColoredBox 有什么区别、什么时候用谁。直接整理成对比表组件职责适用场景SizedBox固定宽高占位、间距、明确尺寸Padding内边距文字和边缘之间留白ColoredBox纯色背景只涂色时不引入多余层级Container组合布局与装饰需要两三个以上能力叠加时这个表回答的是“组件粒度的理解”。选哪个不是简单的性能问题更多是代码语义是否清晰。6.2 用Container看Flutter与其他框架的差异很多人在对比 Flutter 和其他跨端框架时容易陷入“谁性能好”“谁生态大”的笼统讨论。其实用一个 Container 就能看出 Flutter 的核心差异。前端里的类似概念是 div CSS通过样式控制盒模型React Native 的 View 是原生控件做了一层映射Flutter 的 Container 则完全由自己的渲染管线在 GPU 层绘制背景、圆角和阴影不依赖任何原生控件体系。这个设计带来两个结果一是跨端一致性强同一个 Container 在 iOS 和 Android 上画出来的圆角阴影几乎无差别不会出现 WebView 那样各端渲染不一致的问题二是框架本身的渲染机制更“重”所有视觉特性都由自己实现所以裁剪、阴影、纹理这些细节都需要开发者对渲染管线有所了解。这也是为什么很多 Flutter 面试必问 Container因为一道题能带出 Widget 组合、约束、装饰、裁剪、变换等一整套核心知识。我个人在实际项目里的体会是Container 是一个“组合器”不是万能药。单个能力用专一组件复杂视觉用 Container阴影控制着用const 能加就加源码多看几遍。比起背下所有属性理解它内部怎么组合才是从“会写 Flutter”到“懂 Flutter”的分水岭。如果你刚接触 Flutter建议把本指南里的示例代码全部在 DartPad 里敲一遍亲手改几个属性看看效果比只看文章强十倍。
返回列表