第 4 章 核心概念:Widget 与声明式 UI
4.1 声明式 UI:你描述「长什么样」,框架负责「怎么变」
原生 Android(View 体系)是命令式的:拿到 textView 引用,手动调用 setText() 改它。Flutter 是声明式的:UI = f(state),状态变了就重新描述一遍界面,框架自动算出差异并更新。
// 状态(数据)驱动界面。state 变 → build 重新执行 → 界面更新
Text('点击了 $_count 次') // 不需要「拿到 Text 再改它」,永远描述当前该长什么样
4.2 一切皆 Widget
文字是 Widget(Text)、按钮是 Widget(Button)、连边距、居中、行排列也都是 Widget(Padding、Center、Row)——这是与原生最大的思维差异。Widget 组成树:
MaterialApp // 应用
└─ Scaffold // 页面骨架
├─ AppBar // 顶栏
└─ Center // 居中容器
└─ Column // 纵向排列
├─ Text
└─ ElevatedButton
4.3 StatelessWidget 与 StatefulWidget
| StatelessWidget(无状态) | StatefulWidget(有状态) | |
|---|---|---|
| 数据会变吗 | 不会(构造后固定) | 会(配合 setState 更新) |
| 典型场景 | Text、图标按钮、卡片 | 计数器、表单、开关、倒计时 |
| 方法 | build() | createState() + build() |
import 'package:flutter/material.dart';
// 有状态组件三步曲:① 继承 StatefulWidget ② create 持有 State ③ 在 State 里 build
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0; // 状态数据放 State 里
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('计数器')),
body: Center(
child: Text('点击了 $_count 次',
style: const TextStyle(fontSize: 24)),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() { // ④ 改状态必须包在 setState 里,否则界面不刷新!
_count++;
});
},
child: const Icon(Icons.add),
),
);
}
}
void main() => runApp(const MaterialApp(home: CounterPage()));
⚠️ 新手三大高频错误
① 忘记
setState 界面不刷新;② 在 build 里写耗时逻辑(build 会被频繁调用);③ setState 之外直接改 State 字段后预期 UI 更新。记住口诀:数据放 State,改数据用 setState。