Flutter 基础
Flutter 是 Google 开发的开源 UI 工具包,用于构建美观、原生的跨平台应用。本教程将帮助你掌握 Flutter 的基础知识和开发技能。
Flutter 简介
什么是 Flutter?
Flutter 是一个完整的 SDK(软件开发工具包),包含了构建跨平台应用所需的一切:渲染引擎、现成的小部件、集成测试和命令行工具。使用 Flutter,开发者可以用一套代码库构建适用于 Android、iOS、Web、Windows、macOS 和 Linux 的应用。
Flutter 的主要特点
- 跨平台开发:一套代码运行在多个平台
- 热重载:实时查看代码更改的效果,无需重启应用
- 自绘引擎:Flutter 使用自己的渲染引擎 Skia,不依赖于平台的 UI 组件
- 丰富的组件库:Material Design 和 Cupertino(iOS 风格)组件
- 高性能:接近原生应用的性能表现
- 响应式框架:基于响应式编程和组合式 UI 构建
Flutter 与其他框架的比较
特性 | Flutter | React Native | Xamarin |
---|---|---|---|
语言 | Dart | JavaScript | C# |
UI 渲染 | 自绘引擎 | 原生组件 | 原生组件 |
性能 | 优秀 | 良好 | 良好 |
热重载 | 支持 | 支持 | 部分支持 |
社区支持 | 快速增长 | 成熟 | 稳定 |
学习曲线 | 中等 | 中等 | 中等 |
环境搭建
安装 Flutter SDK
Windows
- 下载 Flutter SDK
- 解压到所需位置(如
C:\src\flutter
) - 更新环境变量,添加
flutter\bin
到 Path - 运行
flutter doctor
检查安装状态
macOS
# 使用 Homebrew 安装
brew install --cask flutter
# 或手动安装
git clone https://github.com/flutter/flutter.git
export PATH="$PATH:`pwd`/flutter/bin"
flutter doctor
Linux
# 下载 SDK
tar xf flutter_linux_2.5.3-stable.tar.xz
# 添加到 PATH
export PATH="$PATH:`pwd`/flutter/bin"
# 检查安装
flutter doctor
安装 IDE
Flutter 开发可以使用以下 IDE:
- Visual Studio Code:安装 Flutter 和 Dart 插件
- Android Studio / IntelliJ IDEA:安装 Flutter 和 Dart 插件
- Xcode(仅 macOS,用于 iOS 开发)
创建第一个 Flutter 项目
# 命令行创建
flutter create my_first_app
# 进入项目目录
cd my_first_app
# 运行项目
flutter run
或通过 IDE 创建:
- 打开 VS Code 或 Android Studio
- 选择 "Create New Flutter Project"
- 按照向导完成项目创建
Dart 语言基础
Flutter 使用 Dart 作为开发语言,了解 Dart 基础对 Flutter 开发至关重要。
变量和数据类型
// 变量声明
var name = 'John'; // 类型推断
String lastName = 'Doe'; // 显式类型
dynamic value = 42; // 动态类型
// 常量
final age = 30; // 运行时常量
const PI = 3.14; // 编译时常量
// 基本数据类型
int number = 42;
double price = 9.99;
bool isActive = true;
String text = 'Hello';
// 集合类型
List<String> fruits = ['Apple', 'Banana', 'Orange'];
Set<int> numbers = {1, 2, 3, 4, 5};
Map<String, dynamic> user = {
'name': 'John',
'age': 30,
'isActive': true
};
函数
// 基本函数
int add(int a, int b) {
return a + b;
}
// 箭头函数
int multiply(int a, int b) => a * b;
// 可选参数
void greet(String name, [String? greeting]) {
print('${greeting ?? 'Hello'}, $name!');
}
// 命名参数
void introduce({required String name, int? age}) {
print('My name is $name${age != null ? ' and I am $age years old' : ''}');
}
// 函数作为参数
void performOperation(int a, int b, int Function(int, int) operation) {
print('Result: ${operation(a, b)}');
}
类和对象
class Person {
// 属性
String name;
int age;
// 构造函数
Person(this.name, this.age);
// 命名构造函数
Person.guest() {
name = 'Guest';
age = 0;
}
// 方法
void introduce() {
print('My name is $name and I am $age years old');
}
// Getter
String get description => '$name, $age years old';
// Setter
set setAge(int value) {
if (value >= 0) {
age = value;
}
}
}
// 继承
class Student extends Person {
String school;
Student(String name, int age, this.school) : super(name, age);
@override
void introduce() {
print('My name is $name, I am $age years old, and I study at $school');
}
}
// 接口实现
class Robot implements Person {
@override
String name;
@override
int age;
Robot(this.name, this.age);
@override
void introduce() {
print('I am robot $name');
}
@override
String get description => 'Robot: $name';
@override
set setAge(int value) {
age = value;
}
}
// Mixin
mixin Logger {
void log(String message) {
print('LOG: $message');
}
}
class LoggedPerson extends Person with Logger {
LoggedPerson(String name, int age) : super(name, age);
void performAction() {
log('Person $name performed an action');
}
}
异步编程
// Future
Future<String> fetchData() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
return 'Data loaded';
}
// 使用 async/await
void loadData() async {
print('Loading...');
try {
String result = await fetchData();
print(result);
} catch (e) {
print('Error: $e');
}
}
// Stream
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
// 使用 Stream
void listenToStream() {
final stream = countStream(5);
final subscription = stream.listen(
(data) => print('Received: $data'),
onError: (err) => print('Error: $err'),
onDone: () => print('Stream completed'),
);
// 取消订阅
// subscription.cancel();
}
Flutter 基础组件
基本布局组件
// Container - 矩形视觉元素
Container(
width: 200,
height: 200,
margin: EdgeInsets.all(10),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Text('Hello Flutter'),
)
// Row - 水平布局
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.star, size: 50),
Text('Rating: 4.5'),
ElevatedButton(
onPressed: () {},
child: Text('Rate'),
),
],
)
// Column - 垂直布局
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Title', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Text('Description goes here...'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Learn More'),
),
],
)
// Stack - 层叠布局
Stack(
children: [
Image.network('https://example.com/image.jpg'),
Positioned(
bottom: 10,
right: 10,
child: Container(
padding: EdgeInsets.all(8),
color: Colors.black54,
child: Text(
'Caption',
style: TextStyle(color: Colors.white),
),
),
),
],
)
常用 UI 组件
// Text
Text(
'Hello Flutter',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 1.2,
),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
)
// Button
ElevatedButton(
onPressed: () {
print('Button pressed');
},
style: ElevatedButton.styleFrom(
primary: Colors.blue,
onPrimary: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Click Me'),
)
// TextField
TextField(
decoration: InputDecoration(
labelText: 'Username',
hintText: 'Enter your username',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
onChanged: (value) {
print('Input: $value');
},
)
// Image
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
)
// Card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Card Title', style: TextStyle(fontSize: 20)),
SizedBox(height: 8),
Text('Card content goes here...'),
],
),
),
)
// ListView
ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.star),
title: Text('Item ${index + 1}'),
subtitle: Text('Description for item ${index + 1}'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
print('Tapped on item ${index + 1}');
},
);
},
)
导航和路由
// 基本导航
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
// 返回上一页
Navigator.pop(context);
// 命名路由
Navigator.pushNamed(context, '/second');
// 带参数的命名路由
Navigator.pushNamed(
context,
'/details',
arguments: {'id': 123, 'title': 'Product Details'},
);
// 替换当前路由
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
// 清除所有路由并导航到新页面
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
(route) => false,
);
状态管理
StatefulWidget
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Provider 状态管理
// 1. 定义模型
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 2. 提供模型
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
// 3. 使用模型
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
'${context.watch<CounterModel>().count}',
style: TextStyle(fontSize: 36),
);
}
}
class CounterButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
context.read<CounterModel>().increment();
},
child: Icon(Icons.add),
);
}
}
Flutter 实战
构建完整的 Flutter 应用
项目结构
my_app/
├── android/ # Android 平台特定代码
├── ios/ # iOS 平台特定代码
├── lib/ # Dart 源代码
│ ├── main.dart # 应用入口点
│ ├── models/ # 数据模型
│ ├── screens/ # 页面
│ ├── widgets/ # 可复用组件
│ ├── services/ # 服务(API、数据库等)
│ └── utils/ # 工具函数
├── test/ # 测试代码
├── assets/ # 静态资源(图片、字体等)
└── pubspec.yaml # 项目配置和依赖
主应用结构
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/app_state.dart';
import 'screens/home_screen.dart';
import 'screens/login_screen.dart';
import 'screens/settings_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AppState()),
],
child: MaterialApp(
title: 'My Flutter App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/login': (context) => LoginScreen(),
'/settings': (context) => SettingsScreen(),
},
),
);
}
}
网络请求和数据处理
// 使用 http 包
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List<User>> fetchUsers() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
if (response.statusCode == 200) {
List<dynamic> data = jsonDecode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
// 数据模型
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
// 在 UI 中使用
class UserListScreen extends StatefulWidget {
@override
_UserListScreenState createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
late Future<List<User>> futureUsers;
@override
void initState() {
super.initState();
futureUsers = fetchUsers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Users')),
body: FutureBuilder<List<User>>(
future: futureUsers,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index].name),
subtitle: Text(snapshot.data![index].email),
);
},
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
return Center(child: CircularProgressIndicator());
},
),
);
}
}
本地存储
// 使用 shared_preferences
import 'package:shared_preferences/shared_preferences.dart';
// 保存数据
Future<void> saveUserPreferences(String username, bool isDarkMode) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
await prefs.setBool('isDarkMode', isDarkMode);
}
// 读取数据
Future<Map<String, dynamic>> loadUserPreferences() async {
final prefs = await SharedPreferences.getInstance();
return {
'username': prefs.getString('username') ?? '',
'isDarkMode': prefs.getBool('isDarkMode') ?? false,
};
}
// 使用 SQLite 数据库
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('notes.db');
return _database!;
}
Future<Database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(path, version: 1, onCreate: _createDB);
}
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE notes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
createdAt TEXT NOT NULL
)
''');
}
Future<int> createNote(Note note) async {
final db = await instance.database;
return await db.insert('notes', note.toJson());
}
Future<List<Note>> getNotes() async {
final db = await instance.database;
final result = await db.query('notes');
return result.map((json) => Note.fromJson(json)).toList();
}
}
class Note {
final int? id;
final String title;
final String content;
final DateTime createdAt;
Note({
this.id,
required this.title,
required this.content,
required this.createdAt,
});
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'content': content,
'createdAt': createdAt.toIso8601String(),
};
factory Note.fromJson(Map<String, dynamic> json) => Note(
id: json['id'],
title: json['title'],
content: json['content'],
createdAt: DateTime.parse(json['createdAt']),
);
}
动画
// 基本动画
class AnimatedLogo extends StatefulWidget {
@override
_AnimatedLogoState createState() => _AnimatedLogoState();
}
class _AnimatedLogoState extends State<AnimatedLogo>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
animation = CurvedAnimation(
parent: controller,
curve: Curves.easeInOut,
).drive(Tween<double>(begin: 0, end: 300));
controller.repeat(reverse: true);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(
height: animation.value,
width: animation.value,
child: FlutterLogo(),
);
},
);
}
}
// 内置动画组件
class AnimationDemo extends StatefulWidget {
@override
_AnimationDemoState createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
bool _isExpanded = false;
double _opacity = 1.0;
Color _color = Colors.blue;
@override
Widget build(BuildContext context) {
return Column(
children: [
// 动画容器
AnimatedContainer(
duration: Duration(seconds: 1),
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _color,
curve: Curves.fastOutSlowIn,
),
SizedBox(height: 20),
// 淡入淡出
AnimatedOpacity(
opacity: _opacity,
duration: Duration(milliseconds: 500),
child: Text('Fade In/Out Text'),
),
SizedBox(height: 20),
// 控制按钮
ElevatedButton(
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
_opacity = _opacity == 1.0 ? 0.3 : 1.0;
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
},
child: Text('Animate'),
),
],
);
}
}
Flutter 最佳实践
代码组织
- 遵循单一职责原则:每个类和函数应该只有一个职责
- 使用适当的文件夹结构:按功能或特性组织代码
- 将业务逻辑与 UI 分离:使用 Provider、Bloc 或其他状态管理方案
- 创建可复用组件:将常用 UI 元素抽象为独立组件
性能优化
使用 const 构造函数:减少重建开销
dartconst MyWidget(key: Key('my-widget'))
避免不必要的 setState:只在需要更新 UI 时调用
使用 ListView.builder:处理长列表时按需构建项目
图片优化:使用适当的缓存和占位符
dartCachedNetworkImage( imageUrl: 'https://example.com/image.jpg', placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => Icon(Icons.error), )
延迟加载:使用 FutureBuilder 或 StreamBuilder 延迟加载数据
响应式设计
使用 MediaQuery:获取屏幕尺寸和方向
dartfinal screenWidth = MediaQuery.of(context).size.width; final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
使用 LayoutBuilder:根据父组件约束调整布局
dartLayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth > 600) { return WideLayout(); } else { return NarrowLayout(); } }, )
使用 Flexible 和 Expanded:创建自适应布局
dartRow( children: [ Expanded(flex: 2, child: LeftPanel()), Expanded(flex: 3, child: RightPanel()), ], )
测试
单元测试:测试独立功能和业务逻辑
dartvoid main() { test('Counter increments smoke test', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); }
Widget 测试:测试 UI 组件
dartvoid main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { await tester.pumpWidget(MyApp()); expect(find.text('0'), findsOneWidget); await tester.tap(find.byIcon(Icons.add)); await tester.pump(); expect(find.text('1'), findsOneWidget); }); }
集成测试:测试整个应用流程
dartvoid main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Complete login flow test', (WidgetTester tester) async { app.main(); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField).first, 'username'); await tester.enterText(find.byType(TextField).last, 'password'); await tester.tap(find.byType(ElevatedButton)); await tester.pumpAndSettle(); expect(find.text('Welcome, username!'), findsOneWidget); }); }
Flutter 生态系统
常用包和插件
- http: 网络请求
- provider: 状态管理
- shared_preferences: 简单键值存储
- sqflite: SQLite 数据库
- path_provider: 文件系统访问
- image_picker: 从相册或相机选择图片
- url_launcher: 打开 URL、电话、短信等
- flutter_local_notifications: 本地通知
- firebase_core: Firebase 核心功能
- google_maps_flutter: Google 地图集成
Flutter 开发工具
- Flutter DevTools: 性能分析、调试和检查工具
- Flutter Inspector: 检查和调试 UI
- Flutter Outline: 可视化编辑 UI
- Hot Reload: 实时查看代码更改效果
- Dart Analysis: 代码质量检查
学习资源
官方资源
社区资源
- Flutter 社区中文资源
- Flutter Awesome:精选 Flutter 库和工具
- Pub.dev:Dart 和 Flutter 包仓库
- Stack Overflow Flutter 标签
- Medium Flutter 专题
结语
Flutter 是一个强大而灵活的跨平台开发框架,通过单一代码库实现多平台应用开发。本教程涵盖了 Flutter 的基础知识、核心组件、状态管理、网络请求、本地存储和最佳实践等内容,希望能帮助你开始 Flutter 开发之旅。
随着你的深入学习,你将能够构建更复杂、更精美的应用程序。Flutter 社区非常活跃,有大量的资源和库可供使用,不断学习和实践是掌握 Flutter 的关键。
祝你在 Flutter 开发中取得成功!
如有问题或建议,请通过 GitHub Issues 反馈。
Flutter 基础组件
基本布局组件
// Container - 矩形视觉元素
Container(
width: 200,
height: 200,
margin: EdgeInsets.all(10),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 5,
offset: Offset(0, 3),
),
],
),
child: Text('Hello Flutter'),
)
// Row - 水平布局
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.star, size: 50),
Text('Rating: 4.5'),
ElevatedButton(
onPressed: () {},
child: Text('Rate'),
),
],
)
// Column - 垂直布局
Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('Title', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Text('Description goes here...'),
SizedBox(height: 20),
ElevatedButton(
onPressed: () {},
child: Text('Learn More'),
),
],
)
// Stack - 层叠布局
Stack(
children: [
Image.network('https://example.com/image.jpg'),
Positioned(
bottom: 10,
right: 10,
child: Container(
padding: EdgeInsets.all(8),
color: Colors.black54,
child: Text(
'Caption',
style: TextStyle(color: Colors.white),
),
),
),
],
)
常用 UI 组件
// Text
Text(
'Hello Flutter',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.blue,
letterSpacing: 1.2,
),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 2,
)
// Button
ElevatedButton(
onPressed: () {
print('Button pressed');
},
style: ElevatedButton.styleFrom(
primary: Colors.blue,
onPrimary: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Click Me'),
)
// TextField
TextField(
decoration: InputDecoration(
labelText: 'Username',
hintText: 'Enter your username',
prefixIcon: Icon(Icons.person),
border: OutlineInputBorder(),
),
onChanged: (value) {
print('Input: $value');
},
)
// Image
Image.network(
'https://example.com/image.jpg',
width: 200,
height: 200,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
)
// Card
Card(
elevation: 4,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text('Card Title', style: TextStyle(fontSize: 20)),
SizedBox(height: 8),
Text('Card content goes here...'),
],
),
),
)
// ListView
ListView.builder(
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.star),
title: Text('Item ${index + 1}'),
subtitle: Text('Description for item ${index + 1}'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
print('Tapped on item ${index + 1}');
},
);
},
)
导航和路由
// 基本导航
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
// 返回上一页
Navigator.pop(context);
// 命名路由
Navigator.pushNamed(context, '/second');
// 带参数的命名路由
Navigator.pushNamed(
context,
'/details',
arguments: {'id': 123, 'title': 'Product Details'},
);
// 替换当前路由
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
);
// 清除所有路由并导航到新页面
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeScreen()),
(route) => false,
);
状态管理
StatefulWidget
class CounterScreen extends StatefulWidget {
@override
_CounterScreenState createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Provider 状态管理
// 1. 定义模型
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
// 2. 提供模型
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
// 3. 使用模型
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
'${context.watch<CounterModel>().count}',
style: TextStyle(fontSize: 36),
);
}
}
class CounterButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FloatingActionButton(
onPressed: () {
context.read<CounterModel>().increment();
},
child: Icon(Icons.add),
);
}
}
Flutter 实战
构建完整的 Flutter 应用
项目结构
my_app/
├── android/ # Android 平台特定代码
├── ios/ # iOS 平台特定代码
├── lib/ # Dart 源代码
│ ├── main.dart # 应用入口点
│ ├── models/ # 数据模型
│ ├── screens/ # 页面
│ ├── widgets/ # 可复用组件
│ ├── services/ # 服务(API、数据库等)
│ └── utils/ # 工具函数
├── test/ # 测试代码
├── assets/ # 静态资源(图片、字体等)
└── pubspec.yaml # 项目配置和依赖
主应用结构
// main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'models/app_state.dart';
import 'screens/home_screen.dart';
import 'screens/login_screen.dart';
import 'screens/settings_screen.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AppState()),
],
child: MaterialApp(
title: 'My Flutter App',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/login': (context) => LoginScreen(),
'/settings': (context) => SettingsScreen(),
},
),
);
}
}
网络请求和数据处理
// 使用 http 包
import 'package:http/http.dart' as http;
import 'dart:convert';
Future<List<User>> fetchUsers() async {
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
if (response.statusCode == 200) {
List<dynamic> data = jsonDecode(response.body);
return data.map((json) => User.fromJson(json)).toList();
} else {
throw Exception('Failed to load users');
}
}
// 数据模型
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
// 在 UI 中使用
class UserListScreen extends StatefulWidget {
@override
_UserListScreenState createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
late Future<List<User>> futureUsers;
@override
void initState() {
super.initState();
futureUsers = fetchUsers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Users')),
body: FutureBuilder<List<User>>(
future: futureUsers,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data![index].name),
subtitle: Text(snapshot.data![index].email),
);
},
);
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
}
return Center(child: CircularProgressIndicator());
},
),
);
}
}
本地存储
// 使用 shared_preferences
import 'package:shared_preferences/shared_preferences.dart';
// 保存数据
Future<void> saveUserPreferences(String username, bool isDarkMode) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('username', username);
await prefs.setBool('isDarkMode', isDarkMode);
}
// 读取数据
Future<Map<String, dynamic>> loadUserPreferences() async {
final prefs = await SharedPreferences.getInstance();
return {
'username': prefs.getString('username') ?? '',
'isDarkMode': prefs.getBool('isDarkMode') ?? false,
};
}
// 使用 SQLite 数据库
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('notes.db');
return _database!;
}
Future<Database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(path, version: 1, onCreate: _createDB);
}
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE notes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL,
createdAt TEXT NOT NULL
)
''');
}
Future<int> createNote(Note note) async {
final db = await instance.database;
return await db.insert('notes', note.toJson());
}
Future<List<Note>> getNotes() async {
final db = await instance.database;
final result = await db.query('notes');
return result.map((json) => Note.fromJson(json)).toList();
}
}
class Note {
final int? id;
final String title;
final String content;
final DateTime createdAt;
Note({
this.id,
required this.title,
required this.content,
required this.createdAt,
});
Map<String, dynamic> toJson() => {
'id': id,
'title': title,
'content': content,
'createdAt': createdAt.toIso8601String(),
};
factory Note.fromJson(Map<String, dynamic> json) => Note(
id: json['id'],
title: json['title'],
content: json['content'],
createdAt: DateTime.parse(json['createdAt']),
);
}
动画
// 基本动画
class AnimatedLogo extends StatefulWidget {
@override
_AnimatedLogoState createState() => _AnimatedLogoState();
}
class _AnimatedLogoState extends State<AnimatedLogo>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
animation = CurvedAnimation(
parent: controller,
curve: Curves.easeInOut,
).drive(Tween<double>(begin: 0, end: 300));
controller.repeat(reverse: true);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (context, child) {
return Container(
height: animation.value,
width: animation.value,
child: FlutterLogo(),
);
},
);
}
}
// 内置动画组件
class AnimationDemo extends StatefulWidget {
@override
_AnimationDemoState createState() => _AnimationDemoState();
}
class _AnimationDemoState extends State<AnimationDemo> {
bool _isExpanded = false;
double _opacity = 1.0;
Color _color = Colors.blue;
@override
Widget build(BuildContext context) {
return Column(
children: [
// 动画容器
AnimatedContainer(
duration: Duration(seconds: 1),
width: _isExpanded ? 200 : 100,
height: _isExpanded ? 200 : 100,
color: _color,
curve: Curves.fastOutSlowIn,
),
SizedBox(height: 20),
// 淡入淡出
AnimatedOpacity(
opacity: _opacity,
duration: Duration(milliseconds: 500),
child: Text('Fade In/Out Text'),
),
SizedBox(height: 20),
// 控制按钮
ElevatedButton(
onPressed: () {
setState(() {
_isExpanded = !_isExpanded;
_opacity = _opacity == 1.0 ? 0.3 : 1.0;
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
},
child: Text('Animate'),
),
],
);
}
}
Flutter 最佳实践
代码组织
- 遵循单一职责原则:每个类和函数应该只有一个职责
- 使用适当的文件夹结构:按功能或特性组织代码
- 将业务逻辑与 UI 分离:使用 Provider、Bloc 或其他状态管理方案
- 创建可复用组件:将常用 UI 元素抽象为独立组件
性能优化
使用 const 构造函数:减少重建开销
dartconst MyWidget(key: Key('my-widget'))
避免不必要的 setState:只在需要更新 UI 时调用
使用 ListView.builder:处理长列表时按需构建项目
图片优化:使用适当的缓存和占位符
dartCachedNetworkImage( imageUrl: 'https://example.com/image.jpg', placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => Icon(Icons.error), )
延迟加载:使用 FutureBuilder 或 StreamBuilder 延迟加载数据
响应式设计
使用 MediaQuery:获取屏幕尺寸和方向
dartfinal screenWidth = MediaQuery.of(context).size.width; final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
使用 LayoutBuilder:根据父组件约束调整布局
dartLayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth > 600) { return WideLayout(); } else { return NarrowLayout(); } }, )
使用 Flexible 和 Expanded:创建自适应布局
dartRow( children: [ Expanded(flex: 2, child: LeftPanel()), Expanded(flex: 3, child: RightPanel()), ], )
测试
单元测试:测试独立功能和业务逻辑
dartvoid main() { test('Counter increments smoke test', () { final counter = Counter(); counter.increment(); expect(counter.value, 1); }); }
Widget 测试:测试 UI 组件
dartvoid main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { await tester.pumpWidget(MyApp()); expect(find.text('0'), findsOneWidget); await tester.tap(find.byIcon(Icons.add)); await tester.pump(); expect(find.text('1'), findsOneWidget); }); }
集成测试:测试整个应用流程
dartvoid main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Complete login flow test', (WidgetTester tester) async { app.main(); await tester.pumpAndSettle(); await tester.enterText(find.byType(TextField).first, 'username'); await tester.enterText(find.byType(TextField).last, 'password'); await tester.tap(find.byType(ElevatedButton)); await tester.pumpAndSettle(); expect(find.text('Welcome, username!'), findsOneWidget); }); }
Flutter 生态系统
常用包和插件
- http: 网络请求
- provider: 状态管理
- shared_preferences: 简单键值存储
- sqflite: SQLite 数据库
- path_provider: 文件系统访问
- image_picker: 从相册或相机选择图片
- url_launcher: 打开 URL、电话、短信等
- flutter_local_notifications: 本地通知
- firebase_core: Firebase 核心功能
- google_maps_flutter: Google 地图集成
Flutter 开发工具
Flutter 基础
Flutter 是 Google 开发的开源 UI 工具包,用于构建美观、原生的跨平台应用。本教程将帮助你掌握 Flutter 的基础知识和开发技能。
Flutter 简介
什么是 Flutter?
Flutter 是一个完整的 SDK(软件开发工具包),包含了构建跨平台应用所需的一切:渲染引擎、现成的小部件、集成测试和命令行工具。使用 Flutter,开发者可以用一套代码库构建适用于 Android、iOS、Web、Windows、macOS 和 Linux 的应用。
Flutter 的主要特点
- 跨平台开发:一套代码运行在多个平台
- 热重载:实时查看代码更改的效果,无需重启应用
- 自绘引擎:Flutter 使用自己的渲染引擎 Skia,不依赖于平台的 UI 组件
- 丰富的组件库:Material Design 和 Cupertino(iOS 风格)组件
- 高性能:接近原生应用的性能表现
- 响应式框架:基于响应式编程和组合式 UI 构建
Flutter 与其他框架的比较
特性 | Flutter | React Native | Xamarin |
---|---|---|---|
语言 | Dart | JavaScript | C# |
UI 渲染 | 自绘引擎 | 原生组件 | 原生组件 |
性能 | 优秀 | 良好 | 良好 |
热重载 | 支持 | 支持 | 部分支持 |
社区支持 | 快速增长 | 成熟 | 稳定 |
学习曲线 | 中等 | 中等 | 中等 |
环境搭建
安装 Flutter SDK
Windows
- 下载 Flutter SDK
- 解压到所需位置(如
C:\src\flutter
) - 更新环境变量,添加
flutter\bin
到 Path - 运行
flutter doctor
检查安装状态
macOS
# 使用 Homebrew 安装
brew install --cask flutter
# 或手动安装
git clone https://github.com/flutter/flutter.git
export PATH="$PATH:`pwd`/flutter/bin"
flutter doctor
Linux
# 下载 SDK
tar xf flutter_linux_2.5.3-stable.tar.xz
# 添加到 PATH
export PATH="$PATH:`pwd`/flutter/bin"
# 检查安装
flutter doctor
安装 IDE
Flutter 开发可以使用以下 IDE:
- Visual Studio Code:安装 Flutter 和 Dart 插件
- Android Studio / IntelliJ IDEA:安装 Flutter 和 Dart 插件
- Xcode(仅 macOS,用于 iOS 开发)
创建第一个 Flutter 项目
# 命令行创建
flutter create my_first_app
# 进入项目目录
cd my_first_app
# 运行项目
flutter run
或通过 IDE 创建:
- 打开 VS Code 或 Android Studio
- 选择 "Create New Flutter Project"
- 按照向导完成项目创建
Dart 语言基础
Flutter 使用 Dart 作为开发语言,了解 Dart 基础对 Flutter 开发至关重要。
变量和数据类型
// 变量声明
var name = 'John'; // 类型推断
String lastName = 'Doe'; // 显式类型
dynamic value = 42; // 动态类型
// 常量
final age = 30; // 运行时常量
const PI = 3.14; // 编译时常量
// 基本数据类型
int number = 42;
double price = 9.99;
bool isActive = true;
String text = 'Hello';
// 集合类型
List<String> fruits = ['Apple', 'Banana', 'Orange'];
Set<int> numbers = {1, 2, 3, 4, 5};
Map<String, dynamic> user = {
'name': 'John',
'age': 30,
'isActive': true
};
函数
// 基本函数
int add(int a, int b) {
return a + b;
}
// 箭头函数
int multiply(int a, int b) => a * b;
// 可选参数
void greet(String name, [String? greeting]) {
print('${greeting ?? 'Hello'}, $name!');
}
// 命名参数
void introduce({required String name, int? age}) {
print('My name is $name${age != null ? ' and I am $age years old' : ''}');
}
// 函数作为参数
void performOperation(int a, int b, int Function(int, int) operation) {
print('Result: ${operation(a, b)}');
}
类和对象
class Person {
// 属性
String name;
int age;
// 构造函数
Person(this.name, this.age);
// 命名构造函数
Person.guest() {
name = 'Guest';
age = 0;
}
// 方法
void introduce() {
print('My name is $name and I am $age years old');
}
// Getter
String get description => '$name, $age years old';
// Setter
set setAge(int value) {
if (value >= 0) {
age = value;
}
}
}
// 继承
class Student extends Person {
String school;
Student(String name, int age, this.school) : super(name, age);
@override
void introduce() {
print('My name is $name, I am $age years old, and I study at $school');
}
}
// 接口实现
class Robot implements Person {
@override
String name;
@override
int age;
Robot(this.name, this.age);
@override
void introduce() {
print('I am robot $name');
}
@override
String get description => 'Robot: $name';
@override
set setAge(int value) {
age = value;
}
}
// Mixin
mixin Logger {
void log(String message) {
print('LOG: $message');
}
}
class LoggedPerson extends Person with Logger {
LoggedPerson(String name, int age) : super(name, age);
void performAction() {
log('Person $name performed an action');
}
}
异步编程
// Future
Future<String> fetchData() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
return 'Data loaded';
}
// 使用 async/await
void loadData() async {
print('Loading...');
try {
String result = await fetchData();
print(result);
} catch (e) {
print('Error: $e');
}
}
// Stream
Stream<int> countStream(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
// 使用 Stream
void listenToStream() {
final stream = countStream(5);
final subscription = stream.listen(
(data) => print('Received: $data'),
onError: (err) => print('Error: $err'),
onDone: () => print('Stream completed'),
);
// 取消订阅
// subscription.cancel();
}