嘿,朋友!我是Agnes。今天咱们不整那些虚头巴脑的“为什么选择Flutter”的官方套话,我直接带你上手。既然你要从零开始,那咱们就坐稳了,我把这趟Flutter的旅程给你讲得明明白白,就像咱们坐在咖啡馆里,我指着屏幕一步步演示一样。
很多人问我:“Agnes,Flutter到底难不难?”我的回答是:难在心态,易在上手。只要你懂一点点HTML+CSS的概念,再配上点Dart语法,你就能搞定80%的界面。剩下的20%是异步数据请求,这个咱们后面再说。
准备好了吗?咱们开始吧。
第一阶段:别急着写代码,先搞定“武器库”
在我给你甩代码之前,你得先把环境搭好。这步最烦人,但必须做。
1. 下载 Flutter SDK
去 Flutter官网 下载对应你系统的版本。别下那种“整合包”,直接下SDK,干净。
2. 安装 VS Code 或 Android Studio
我是VS Code的死忠粉,轻量、插件多。但如果你是Java出身,Android Studio更亲切。随便你,别纠结这个。
3. 关键一步:配置环境变量
Windows用户注意:
- 把
flutter/bin加到你的PATH里。 - 重启终端,输入
flutter doctor。
敲黑板! 如果你看到红叉,别慌,跟着提示装。最常见的问题:
- Android License failed:去装Android SDK,然后在Flutter目录运行
flutter doctor --android-licenses,一路按Y。 - Chrome not found:如果你是Web开发,装个Chrome;如果只做移动端,忽略。
现在,你的终端应该长这样:
$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.x.x, on macOS ...)
[✓] Android toolchain - develop for Android devices (Android SDK version ...)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio
[✓] Connected device
全绿了?好,咱们走起。
第二阶段:你的第一个Flutter程序(Hello World)
别以为创建项目很简单。我见过太多人卡在项目创建这一步。
在终端运行:
flutter create my_first_app
cd my_first_app
然后,打开 lib/main.dart。删掉里面所有的代码,换成这个:
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(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
useMaterial3: true, // 开启Material 3,界面更现代
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agnes的Flutter首战'),
backgroundColor: Colors.teal,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Icon(
Icons.flutter_dash,
size: 100,
color: Colors.teal,
),
const SizedBox(height: 20),
const Text(
'Hello, Flutter!',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
const Text(
'这是Agnes带你写的第一行代码',
style: TextStyle(color: Colors.grey),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
print('按钮被点击了!');
},
child: const Icon(Icons.add),
),
);
}
}
这里的门道
runApp:这是入口,就像React的ReactDOM.render。它把你的根Widget挂上去。MaterialApp:配置主题、路由等。现在默认开启useMaterial3,按钮、卡片看起来会更“新”。Scaffold:这是App的骨架。它有固定的结构:Appbar、Body、FloatingActionButton、Drawer、BottomNavigationBar等。别试图不用Scaffold写主流界面,累死你。StatelessWidget:无状态组件。它的界面不会因为数据变化而改变。先学这个,因为它简单。const关键字:注意看,Widget构造函数都加了const。这很重要,告诉Flutter这个Widget是常量,不用每次重建。
运行它(确保你的手机或模拟器已连接):
flutter run
如果屏幕上出现了“Hello, Flutter!”,恭喜你,你跨过第一步了。
第三阶段:理解核心概念——“Widget即一切”
Flutter里,没有XML,没有Storyboard,没有Jetpack Compose的复杂概念。你看到的一切都是Widget。
1. Widget是树
你可以把整个App想象成一棵倒着的树。根是MaterialApp,枝叶是各种Container、Text、Row、Column。
Widget build(BuildContext context) {
return Container( // 容器
child: Column( // 垂直排列
children: [
Text('第一行'), // 文本
Icon(Icons.star), // 图标
],
),
);
}
2. 布局三剑客:Padding、Container、SizedBox
Padding:加内边距。Container:加外边距、颜色、宽高、装饰。SizedBox:加固定大小的空白。
实战中,我最常用SizedBox(height: 16)来代替<br>标签。
3. 行与列:Row 和 Column
这是移动端布局最常用的两个Widget。
Row:水平排列子元素。Column:垂直排列子元素。
关键参数:
mainAxisAlignment:主轴对齐(Row是水平,Column是垂直)。crossAxisAlignment:交叉轴对齐。
举个例子,做一个登录表单:
Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: '邮箱',
prefixIcon: Icon(Icons.email),
),
),
const SizedBox(height: 16), // 间距
TextField(
obscureText: true, // 密码框
decoration: const InputDecoration(
labelText: '密码',
prefixIcon: Icon(Icons.lock),
),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {},
child: const Text('登录'),
),
],
)
看,是不是很像写HTML?Input标签变成了TextField,div变成了Column。
第四阶段:状态管理——setState 是真的香
很多教程上来就讲Provider、Bloc、Riverpod,我劝你别急。
在你学会用setState之前,不要碰那些复杂的库。setState是Flutter的魂。
1. 什么是StatefulWidget?
和StatelessWidget不同,StatefulWidget内部状态可以变化,UI会跟着刷新。
2. 计数器示例(经典但有效)
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0; // 状态变量
void _increment() {
setState(() {
_count++; // 状态改变,UI自动刷新
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('状态管理入门')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'你点击了这么多次',
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 10),
Text(
'$_count',
style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _increment,
child: const Icon(Icons.add),
),
);
}
}
注意点:
- 状态变量必须定义在
State类里,不是Widget类。 - 改变状态必须调用
setState,否则UI不会更新。 _count前面加下划线,表示它是私有变量。
第五阶段:网络请求——从API获取数据
光有静态页面不够,真正的App都要联网。Flutter用http包或者dio包。我推荐先学http,简单直接;项目大了再用dio。
1. 添加依赖
在pubspec.yaml里加:
dependencies:
flutter:
sdk: flutter
http: ^1.1.0 # 加上这一行
然后运行:
flutter pub get
2. 封装一个Service
别在Widget里直接写网络请求,太乱。建一个services/user_service.dart:
import 'dart:convert';
import 'package:http/http.dart' as http;
class UserService {
static const String baseUrl = 'https://jsonplaceholder.typicode.com';
Future<List<Map<String, dynamic>>> getUsers() async {
final response = await http.get(Uri.parse('$baseUrl/users'));
if (response.statusCode == 200) {
List<dynamic> data = jsonDecode(response.body);
return data.cast<Map<String, dynamic>>();
} else {
throw Exception('Failed to load users');
}
}
}
3. 在Widget里调用
用FutureBuilder,这是处理异步数据的利器。
import 'package:flutter/material.dart';
import '../services/user_service.dart';
class UserListPage extends StatelessWidget {
final UserService _userService = UserService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('用户列表')),
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _userService.getUsers(),
builder: (context, snapshot) {
// 加载中
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
// 错误处理
if (snapshot.hasError) {
return Center(child: Text('出错了: ${snapshot.error}'));
}
// 数据就绪
if (snapshot.hasData) {
final users = snapshot.data!;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
leading: const Icon(Icons.person),
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
);
}
return const Center(child: Text('暂无数据'));
},
),
);
}
}
这里有个坑: FutureBuilder在每次build时都会检查future。如果你的future是动态的(比如搜索),每次输入都要重新发请求,没问题。但如果future是固定的,FutureBuilder只会请求一次,之后不会重新请求。这是正常的,也是高效的。
第六阶段:路由导航——页面跳转
Flutter的路由分两种:命名路由和即时路由。我推荐用命名路由,更清晰。
1. 定义路由
在main.dart的MaterialApp里配置:
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/user': (context) => const UserDetailPage(),
},
)
2. 跳转到新页面
Navigator.pushNamed(context, '/user');
3. 携带参数跳转
Flutter不像Android那样有Bundle,也不像React Native有props。我们通常用构造函数传参。
修改UserDetailPage:
class UserDetailPage extends StatelessWidget {
final String userId;
final String userName;
const UserDetailPage({
super.key,
required this.userId,
required this.userName,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('用户详情: $userName')),
body: Center(
child: Text('用户ID: $userId'),
),
);
}
}
然后在路由表里注册,并用工厂构造函数创建:
routes: {
'/user': (context) => const UserDetailPage(userId: '1', userName: '张三'),
}
更通用的做法是用arguments:
// 跳转时
Navigator.pushNamed(
context,
'/user',
arguments: {'id': '1', 'name': '张三'},
);
// 接收时
class UserDetailPage extends StatelessWidget {
const UserDetailPage({super.key});
@override
Widget build(BuildContext context) {
// 获取参数
final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>;
final userId = args['id'] as String;
final userName = args['name'] as String;
// ... 构建UI
}
}
第七阶段:状态管理进阶——Provider
当你有10个页面,每个页面都要用同一套用户数据时,setState就不够用了。这时候需要状态管理。
Flutter官方推荐的是Provider。它基于InheritedWidget,简单易懂。
1. 添加依赖
dependencies:
provider: ^6.1.1
2. 创建一个ChangeNotifier
import 'package:flutter/material.dart';
class UserProvider extends ChangeNotifier {
List<Map<String, dynamic>> _users = [];
bool _isLoading = false;
List<Map<String, dynamic>> get users => _users;
bool get isLoading => _isLoading;
Future<void> fetchUsers() async {
_isLoading = true;
notifyListeners(); // 通知UI刷新
// 模拟网络请求
await Future.delayed(const Duration(seconds: 1));
_users = [
{'id': '1', 'name': 'Alice', 'email': 'alice@example.com'},
{'id': '2', 'name': 'Bob', 'email': 'bob@example.com'},
];
_isLoading = false;
notifyListeners();
}
}
3. 在根Widget包裹Provider
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserProvider()),
],
child: const MyApp(),
),
);
}
4. 在子Widget中使用
class UserListPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final provider = context.watch<UserProvider>(); // 监听变化
return Scaffold(
appBar: AppBar(title: const Text('用户列表')),
body: provider.isLoading
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: provider.users.length,
itemBuilder: (context, index) {
final user = provider.users[index];
return ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => provider.fetchUsers(),
child: const Icon(Icons.refresh),
),
);
}
}
注意: context.watch会让Widget在Provider变化时重建。如果你只是想读数据,不想监听,用context.read。
第八阶段:本地存储——SharedPreferences
数据持久化是App的基本需求。Flutter官方推荐shared_preferences包。
1. 添加依赖
dependencies:
shared_preferences: ^2.2.2
2. 封装一个Storage类
import 'package:shared_preferences/shared_preferences.dart';
class StorageService {
static const String _keyToken = 'auth_token';
Future<void> saveToken(String token) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_keyToken, token);
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(_keyToken);
}
Future<void> clearToken() async {
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_keyToken);
}
}
3. 使用
final storage = StorageService();
await storage.saveToken('abc123');
final token = await storage.getToken();
限制: SharedPreferences适合存小数据(Token、设置、用户偏好)。别用它存大量数据,用SQLite(sqflite包)或ObjectBox。
第九阶段:UI组件大全——Material vs Cupertino
Flutter
