Flutter作为Google推出的跨平台框架,一套代码同时运行在iOS、Android、Web和桌面端。本文将深入剖析Flutter开发中的核心技巧和常见痛点,助你快速掌握Flutter开发。
一、Flutter开发的常见痛点
1.1 状态管理混乱
Flutter的状态管理方案众多(Provider、Riverpod、Bloc、GetX),新手容易迷失。
痛点表现:
- 组件间数据传递复杂
- 状态更新导致整个页面重建
- 代码耦合度高,难以维护
1.2 性能优化困难
- 列表滚动卡顿
- 页面切换动画不流畅
- 内存占用过高
1.3 原生交互复杂
需要编写Platform Channel与原生代码通信,学习成本高。
二、核心开发技巧
2.1 StatelessWidget vs StatefulWidget
关键点: 理解何时使用无状态组件和有状态组件是Flutter开发的基础。
dart
// ❌ 错误:不需要状态却使用StatefulWidget
class MyButton extends StatefulWidget {
final String text;
MyButton({required this.text});
@override
_MyButtonState createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {},
child: Text(widget.text),
);
}
}
// ✅ 正确:纯展示组件使用StatelessWidget
class MyButton extends StatelessWidget {
final String text;
final VoidCallback? onPressed;
const MyButton({
Key? key,
required this.text,
this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onPressed,
child: Text(text),
);
}
}
// 有状态组件示例:计数器
class Counter extends StatefulWidget {
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('计数: $_count', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: _increment,
child: Text('增加'),
),
],
);
}
}
痛点解决: 合理选择组件类型可以减少不必要的重建,提升性能30%以上。
2.2 Provider状态管理最佳实践
关键点: Provider是Flutter官方推荐的状态管理方案,简单易用。
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 1. 定义数据模型
class UserModel extends ChangeNotifier {
String _name = '';
int _age = 0;
String get name => _name;
int get age => _age;
void updateUser(String name, int age) {
_name = name;
_age = age;
notifyListeners(); // 通知监听者更新
}
}
// 2. 在顶层提供状态
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => UserModel(),
child: MyApp(),
),
);
}
// 3. 消费状态
class UserProfile extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ❌ 错误:整个组件都会重建
final user = Provider.of<UserModel>(context);
return Column(
children: [
Text('姓名: ${user.name}'),
Text('年龄: ${user.age}'),
],
);
}
}
// ✅ 正确:使用Consumer精确控制重建范围
class UserProfileOptimized extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
// 只有这部分会重建
Consumer<UserModel>(
builder: (context, user, child) {
return Text('姓名: ${user.name}');
},
),
// 静态内容不会重建
Text('其他信息'),
],
);
}
}
// 4. 修改状态
class EditUserPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// listen: false 避免不必要的监听
Provider.of<UserModel>(context, listen: false)
.updateUser('张三', 25);
},
child: Text('更新用户'),
);
}
}
// 多个Provider组合使用
void mainMultiProvider() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => UserModel()),
ChangeNotifierProvider(create: (_) => CartModel()),
ChangeNotifierProvider(create: (_) => ThemeModel()),
],
child: MyApp(),
),
);
}
痛点解决: 清晰的状态管理避免了props层层传递,代码可维护性提升50%。
2.3 ListView性能优化
关键点: 正确使用ListView.builder实现懒加载,避免一次性渲染所有元素。
dart
// ❌ 错误:一次性渲染所有元素,数据量大时会卡顿
class BadListView extends StatelessWidget {
final List<String> items = List.generate(10000, (i) => '项目 $i');
@override
Widget build(BuildContext context) {
return ListView(
children: items.map((item) => ListTile(title: Text(item))).toList(),
);
}
}
// ✅ 正确:使用builder按需构建
class GoodListView extends StatelessWidget {
final List<String> items = List.generate(10000, (i) => '项目 $i');
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
);
}
}
// 分隔符列表
class SeparatedListView extends StatelessWidget {
final List<String> items = List.generate(100, (i) => '项目 $i');
@override
Widget build(BuildContext context) {
return ListView.separated(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text(items[index]),
subtitle: Text('这是第 ${index + 1} 项'),
);
},
separatorBuilder: (context, index) => Divider(),
);
}
}
// 下拉刷新和上拉加载
class RefreshableListView extends StatefulWidget {
@override
_RefreshableListViewState createState() => _RefreshableListViewState();
}
class _RefreshableListViewState extends State<RefreshableListView> {
List<String> items = List.generate(20, (i) => '项目 $i');
bool isLoading = false;
Future<void> _onRefresh() async {
await Future.delayed(Duration(seconds: 2));
setState(() {
items = List.generate(20, (i) => '新项目 $i');
});
}
void _loadMore() {
if (isLoading) return;
setState(() {
isLoading = true;
});
Future.delayed(Duration(seconds: 2), () {
setState(() {
items.addAll(List.generate(20, (i) => '更多项目 ${items.length + i}'));
isLoading = false;
});
});
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _onRefresh,
child: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
// 加载更多指示器
if (!isLoading) _loadMore();
return Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
);
}
return ListTile(title: Text(items[index]));
},
),
);
}
}
痛点解决: 渲染10000条数据时,性能提升100倍,内存占用减少95%。
2.4 异步操作与FutureBuilder
关键点: 优雅处理异步数据加载,避免回调地狱。
dart
import 'dart:convert';
import 'package:http/http.dart' as http;
// 数据模型
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'],
);
}
}
// ❌ 错误:手动管理加载状态
class BadAsyncWidget extends StatefulWidget {
@override
_BadAsyncWidgetState createState() => _BadAsyncWidgetState();
}
class _BadAsyncWidgetState extends State<BadAsyncWidget> {
User? user;
bool isLoading = true;
String? error;
@override
void initState() {
super.initState();
fetchUser();
}
Future<void> fetchUser() async {
try {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users/1')
);
setState(() {
user = User.fromJson(json.decode(response.body));
isLoading = false;
});
} catch (e) {
setState(() {
error = e.toString();
isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
if (isLoading) return CircularProgressIndicator();
if (error != null) return Text('错误: $error');
return Text('用户: ${user!.name}');
}
}
// ✅ 正确:使用FutureBuilder
class GoodAsyncWidget extends StatelessWidget {
Future<User> fetchUser() async {
final response = await http.get(
Uri.parse('https://jsonplaceholder.typicode.com/users/1')
);
if (response.statusCode == 200) {
return User.fromJson(json.decode(response.body));
} else {
throw Exception('加载失败');
}
}
@override
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: fetchUser(),
builder: (context, snapshot) {
// 加载中
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
// 错误处理
if (snapshot.hasError) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, color: Colors.red, size: 60),
SizedBox(height: 16),
Text('错误: ${snapshot.error}'),
ElevatedButton(
onPressed: () {
// 重新加载
(context as Element).markNeedsBuild();
},
child: Text('重试'),
),
],
),
);
}
// 成功加载
if (snapshot.hasData) {
final user = snapshot.data!;
return Card(
child: ListTile(
leading: CircleAvatar(child: Text(user.name[0])),
title: Text(user.name),
subtitle: Text(user.email),
),
);
}
return Center(child: Text('暂无数据'));
},
);
}
}
// StreamBuilder示例:实时数据流
class RealtimeCounter extends StatelessWidget {
Stream<int> counterStream() async* {
int count = 0;
while (true) {
await Future.delayed(Duration(seconds: 1));
yield count++;
}
}
@override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: counterStream(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
'计数: ${snapshot.data}',
style: TextStyle(fontSize: 48),
);
}
return CircularProgressIndicator();
},
);
}
}
痛点解决: 代码量减少40%,自动处理加载、错误、成功状态。
2.5 导航与路由管理
关键点: 掌握命名路由和参数传递,构建清晰的页面导航结构。
dart
// 1. 定义路由
class AppRoutes {
static const String home = '/';
static const String detail = '/detail';
static const String profile = '/profile';
}
// 2. 配置路由表
void main() {
runApp(MaterialApp(
initialRoute: AppRoutes.home,
routes: {
AppRoutes.home: (context) => HomePage(),
AppRoutes.detail: (context) => DetailPage(),
AppRoutes.profile: (context) => ProfilePage(),
},
// 处理未定义路由
onUnknownRoute: (settings) {
return MaterialPageRoute(
builder: (context) => NotFoundPage(),
);
},
));
}
// 3. 基础导航
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ❌ 直接跳转,无法传参
ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, AppRoutes.detail);
},
child: Text('跳转到详情'),
),
// ✅ 传递参数
ElevatedButton(
onPressed: () {
Navigator.pushNamed(
context,
AppRoutes.detail,
arguments: {'id': 123, 'title': '商品详情'},
);
},
child: Text('跳转并传参'),
),
// 等待返回结果
ElevatedButton(
onPressed: () async {
final result = await Navigator.pushNamed(
context,
AppRoutes.profile,
);
print('返回结果: $result');
},
child: Text('跳转并等待返回'),
),
],
),
),
);
}
}
// 4. 接收参数
class DetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 接收路由参数
final args = ModalRoute.of(context)!.settings.arguments as Map?;
final id = args?['id'] ?? 0;
final title = args?['title'] ?? '默认标题';
return Scaffold(
appBar: AppBar(title: Text(title)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('ID: $id', style: TextStyle(fontSize: 24)),
ElevatedButton(
onPressed: () {
Navigator.pop(context); // 返回上一页
},
child: Text('返回'),
),
],
),
),
);
}
}
// 5. 返回数据
class ProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('个人中心')),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context, {'name': '张三', 'age': 25});
},
child: Text('保存并返回'),
),
),
);
}
}
// 6. 替换当前页面(不可返回)
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// 登录成功后替换为首页
Navigator.pushReplacementNamed(context, AppRoutes.home);
},
child: Text('登录'),
);
}
}
// 7. 清空导航栈
class LogoutButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// 退出登录,清空所有页面,跳转到登录页
Navigator.pushNamedAndRemoveUntil(
context,
'/login',
(route) => false, // 移除所有路由
);
},
child: Text('退出登录'),
);
}
}
痛点解决: 统一管理路由,避免硬编码,支持深度链接和Web路由。
2.6 表单验证与输入处理
关键点: 使用Form和TextFormField实现优雅的表单验证。
dart
class LoginForm extends StatefulWidget {
@override
_LoginFormState createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _obscurePassword = true;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
String? _validateEmail(String? value) {
if (value == null || value.isEmpty) {
return '请输入邮箱';
}
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) {
return '邮箱格式不正确';
}
return null;
}
String? _validatePassword(String? value) {
if (value == null || value.isEmpty) {
return '请输入密码';
}
if (value.length < 6) {
return '密码至少6位';
}
return null;
}
void _handleSubmit() {
if (_formKey.currentState!.validate()) {
// 验证通过,执行登录
final email = _emailController.text;
final password = _passwordController.text;
print('登录: $email / $password');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('登录成功')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('登录')),
body: Padding(
padding: EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
children: [
// 邮箱输入
TextFormField(
controller: _emailController,
decoration: InputDecoration(
labelText: '邮箱',
hintText: '请输入邮箱地址',
prefixIcon: Icon(Icons.email),
border: OutlineInputBorder(),
),
keyboardType: TextInputType.emailAddress,
validator: _validateEmail,
),
SizedBox(height: 16),
// 密码输入
TextFormField(
controller: _passwordController,
decoration: InputDecoration(
labelText: '密码',
hintText: '请输入密码',
prefixIcon: Icon(Icons.lock),
suffixIcon: IconButton(
icon: Icon(
_obscurePassword ? Icons.visibility : Icons.visibility_off,
),
onPressed: () {
setState(() {
_obscurePassword = !_obscurePassword;
});
},
),
border: OutlineInputBorder(),
),
obscureText: _obscurePassword,
validator: _validatePassword,
),
SizedBox(height: 24),
// 提交按钮
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: _handleSubmit,
child: Text('登录', style: TextStyle(fontSize: 18)),
),
),
],
),
),
),
);
}
}
痛点解决: 统一的表单验证逻辑,减少重复代码,提升用户体验。
三、实战案例:构建一个完整的Todo应用
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// 1. 数据模型
class Todo {
final String id;
String title;
bool isDone;
Todo({
required this.id,
required this.title,
this.isDone = false,
});
}
// 2. 状态管理
class TodoModel extends ChangeNotifier {
List<Todo> _todos = [];
List<Todo> get todos => _todos;
int get totalCount => _todos.length;
int get doneCount => _todos.where((t) => t.isDone).length;
int get activeCount => totalCount - doneCount;
void addTodo(String title) {
_todos.add(Todo(
id: DateTime.now().toString(),
title: title,
));
notifyListeners();
}
void toggleTodo(String id) {
final todo = _todos.firstWhere((t) => t.id == id);
todo.isDone = !todo.isDone;
notifyListeners();
}
void deleteTodo(String id) {
_todos.removeWhere((t) => t.id == id);
notifyListeners();
}
void updateTodo(String id, String newTitle) {
final todo = _todos.firstWhere((t) => t.id == id);
todo.title = newTitle;
notifyListeners();
}
}
// 3. 主页面
class TodoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => TodoModel(),
child: MaterialApp(
title: 'Todo应用',
theme: ThemeData(primarySwatch: Colors.blue),
home: TodoHomePage(),
),
);
}
}
class TodoHomePage extends StatelessWidget {
final _textController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('我的待办'),
actions: [
Consumer<TodoModel>(
builder: (context, model, child) {
return Center(
child: Padding(
padding: EdgeInsets.only(right: 16),
child: Text(
'${model.doneCount}/${model.totalCount}',
style: TextStyle(fontSize: 16),
),
),
);
},
),
],
),
body: Column(
children: [
// 输入框
Padding(
padding: EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: TextField(
controller: _textController,
decoration: InputDecoration(
hintText: '添加新任务...',
border: OutlineInputBorder(),
),
onSubmitted: (value) {
if (value.isNotEmpty) {
context.read<TodoModel>().addTodo(value);
_textController.clear();
}
},
),
),
SizedBox(width: 8),
IconButton(
icon: Icon(Icons.add_circle, size: 40),
color: Colors.blue,
onPressed: () {
if (_textController.text.isNotEmpty) {
context.read<TodoModel>().addTodo(_textController.text);
_textController.clear();
}
},
),
],
),
),
// 任务列表
Expanded(
child: Consumer<TodoModel>(
builder: (context, model, child) {
if (model.todos.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.inbox, size: 100, color: Colors.grey),
SizedBox(height: 16),
Text('暂无任务', style: TextStyle(fontSize: 18, color: Colors.grey)),
],
),
);
}
return ListView.builder(
itemCount: model.todos.length,
itemBuilder: (context, index) {
final todo = model.todos[index];
return TodoItem(todo: todo);
},
);
},
),
),
],
),
);
}
}
// 4. 任务项组件
class TodoItem extends StatelessWidget {
final Todo todo;
const TodoItem({Key? key, required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
return Dismissible(
key: Key(todo.id),
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: EdgeInsets.only(right: 20),
child: Icon(Icons.delete, color: Colors.white),
),
direction: DismissDirection.endToStart,
onDismissed: (_) {
context.read<TodoModel>().deleteTodo(todo.id);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('已删除: ${todo.title}')),
);
},
child: ListTile(
leading: Checkbox(
value: todo.isDone,
onChanged: (_) {
context.read<TodoModel>().toggleTodo(todo.id);
},
),
title: Text(
todo.title,
style: TextStyle(
decoration: todo.isDone ? TextDecoration.lineThrough : null,
color: todo.isDone ? Colors.grey : Colors.black,
),
),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () {
_showEditDialog(context, todo);
},
),
),
);
}
void _showEditDialog(BuildContext context, Todo todo) {
final controller = TextEditingController(text: todo.title);
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('编辑任务'),
content: TextField(
controller: controller,
autofocus: true,
decoration: InputDecoration(hintText: '任务内容'),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
if (controller.text.isNotEmpty) {
context.read<TodoModel>().updateTodo(todo.id, controller.text);
Navigator.pop(context);
}
},
child: Text('保存'),
),
],
);
},
);
}
}
void main() {
runApp(TodoApp());
}
四、原生功能集成
4.1 使用Platform Channel调用原生代码
关键点: 通过MethodChannel实现Flutter与原生代码的双向通信。
dart
import 'package:flutter/services.dart';
class NativeBridge {
static const platform = MethodChannel('com.example.app/native');
// 调用原生方法
static Future<String> getBatteryLevel() async {
try {
final int result = await platform.invokeMethod('getBatteryLevel');
return '电量: $result%';
} on PlatformException catch (e) {
return '获取失败: ${e.message}';
}
}
// 传递参数到原生
static Future<void> showNativeToast(String message) async {
try {
await platform.invokeMethod('showToast', {'message': message});
} catch (e) {
print('调用失败: $e');
}
}
}
// 使用示例
class NativeFeaturePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('原生功能')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () async {
final battery = await NativeBridge.getBatteryLevel();
print(battery);
},
child: Text('获取电量'),
),
ElevatedButton(
onPressed: () {
NativeBridge.showNativeToast('Hello from Flutter!');
},
child: Text('显示原生Toast'),
),
],
),
),
);
}
}
Android端实现(MainActivity.kt):
kotlin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.widget.Toast
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.app/native"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"getBatteryLevel" -> {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "电量不可用", null)
}
}
"showToast" -> {
val message = call.argument<String>("message")
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
result.success(null)
}
else -> result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
return batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
}
}
iOS端实现(AppDelegate.swift):
swift
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(
name: "com.example.app/native",
binaryMessenger: controller.binaryMessenger
)
channel.setMethodCallHandler { [weak self] (call, result) in
switch call.method {
case "getBatteryLevel":
self?.receiveBatteryLevel(result: result)
case "showToast":
if let args = call.arguments as? [String: Any],
let message = args["message"] as? String {
self?.showToast(message: message)
result(nil)
}
default:
result(FlutterMethodNotImplemented)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
private func receiveBatteryLevel(result: FlutterResult) {
UIDevice.current.isBatteryMonitoringEnabled = true
let batteryLevel = Int(UIDevice.current.batteryLevel * 100)
result(batteryLevel)
}
private func showToast(message: String) {
// iOS没有原生Toast,这里简单实现
print("Toast: \(message)")
}
}
痛点解决: 无缝调用原生功能,如相机、蓝牙、支付等。
4.2 常用第三方插件推荐
yaml
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
# 状态管理
provider: ^6.0.5
# 网络请求
dio: ^5.3.3
# 本地存储
shared_preferences: ^2.2.2
# 图片加载
cached_network_image: ^3.3.0
# 路由管理
go_router: ^12.0.0
# 图片选择
image_picker: ^1.0.4
# 权限管理
permission_handler: ^11.0.1
# 二维码扫描
qr_code_scanner: ^1.0.1
# 地图定位
geolocator: ^10.1.0
# 视频播放
video_player: ^2.8.1
使用示例:
dart
import 'package:dio/dio.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
// 网络请求封装
class ApiService {
static final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
));
static Future<Map<String, dynamic>> get(String path) async {
try {
final response = await dio.get(path);
return response.data;
} catch (e) {
throw Exception('请求失败: $e');
}
}
static Future<Map<String, dynamic>> post(
String path,
Map<String, dynamic> data,
) async {
try {
final response = await dio.post(path, data: data);
return response.data;
} catch (e) {
throw Exception('请求失败: $e');
}
}
}
// 本地存储工具
class StorageUtil {
static Future<void> saveString(String key, String value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString(key, value);
}
static Future<String?> getString(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(key);
}
static Future<void> saveBool(String key, bool value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(key, value);
}
static Future<bool?> getBool(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(key);
}
}
// 图片加载组件
class NetworkImageWidget extends StatelessWidget {
final String imageUrl;
const NetworkImageWidget({Key? key, required this.imageUrl}) : super(key: key);
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: imageUrl,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
fit: BoxFit.cover,
);
}
}
五、性能优化技巧
5.1 使用const构造函数
dart
// ❌ 每次build都创建新对象
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Text('Hello'),
);
}
}
// ✅ 使用const,对象只创建一次
class MyWidget extends StatelessWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Text('Hello');
}
}
5.2 避免在build方法中执行耗时操作
dart
// ❌ 错误:每次build都计算
class BadWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final expensiveData = calculateExpensiveData(); // 耗时操作
return Text(expensiveData);
}
}
// ✅ 正确:缓存计算结果
class GoodWidget extends StatefulWidget {
@override
_GoodWidgetState createState() => _GoodWidgetState();
}
class _GoodWidgetState extends State<GoodWidget> {
late String cachedData;
@override
void initState() {
super.initState();
cachedData = calculateExpensiveData(); // 只计算一次
}
@override
Widget build(BuildContext context) {
return Text(cachedData);
}
}
5.3 使用RepaintBoundary隔离重绘
dart
class OptimizedList extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
// 每个item独立重绘,不影响其他item
return RepaintBoundary(
child: ListTile(
title: Text('Item $index'),
),
);
},
);
}
}
六、调试技巧
6.1 使用Flutter DevTools
bash
# 启动DevTools
flutter pub global activate devtools
flutter pub global run devtools
# 运行应用并连接DevTools
flutter run --observatory-port=9200
6.2 性能分析
dart
import 'package:flutter/foundation.dart';
void main() {
// 开启性能叠加层
debugPaintSizeEnabled = false; // 显示组件边界
debugPaintBaselinesEnabled = false; // 显示文本基线
debugPaintPointersEnabled = false; // 显示触摸点
runApp(MyApp());
}
// 打印build日志
class DebugWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
if (kDebugMode) {
print('DebugWidget build');
}
return Container();
}
}
6.3 常用调试命令
bash
# 热重载
r
# 热重启
R
# 显示性能叠加层
P
# 显示组件边界
p
# 截图
s
# 查看日志
flutter logs
# 分析包大小
flutter build apk --analyze-size
七、打包发布
7.1 Android打包
bash
# 生成签名密钥
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
# 配置签名(android/key.properties)
storePassword=your_password
keyPassword=your_password
keyAlias=key
storeFile=/path/to/key.jks
# 打包APK
flutter build apk --release
# 打包App Bundle(推荐)
flutter build appbundle --release
7.2 iOS打包
bash
# 打包IPA
flutter build ios --release
# 使用Xcode打开项目
open ios/Runner.xcworkspace
7.3 优化包体积
yaml
# pubspec.yaml
flutter:
uses-material-design: true
# 只包含需要的字体
fonts:
- family: Roboto
fonts:
- asset: fonts/Roboto-Regular.ttf
# 移除未使用的资源
flutter:
assets:
- assets/images/logo.png # 只包含使用的图片
bash
# 分析包大小
flutter build apk --analyze-size --target-platform android-arm64
# 启用代码混淆
flutter build apk --obfuscate --split-debug-info=/<project-name>/<directory>
八、常见问题与解决方案
8.1 键盘遮挡输入框
dart
Scaffold(
resizeToAvoidBottomInset: true, // 自动调整布局
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: YourForm(),
),
),
)
8.2 图片内存溢出
dart
// 使用缓存和压缩
Image.network(
imageUrl,
cacheWidth: 500, // 限制缓存宽度
cacheHeight: 500, // 限制缓存高度
)
8.3 Android返回键处理
dart
WillPopScope(
onWillPop: () async {
// 返回false阻止返回,true允许返回
final shouldPop = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('确认退出?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text('确定'),
),
],
),
);
return shouldPop ?? false;
},
child: YourPage(),
)
九、总结
Flutter开发的核心要点:
- 组件化思维 - 一切皆Widget,合理拆分组件
- 状态管理 - 选择合适的方案(Provider、Bloc、Riverpod)
- 性能优化 - const、ListView.builder、RepaintBoundary
- 异步处理 - FutureBuilder、StreamBuilder优雅处理异步
- 原生集成 - Platform Channel实现原生功能
- 持续学习 - Flutter生态快速发展,保持学习
记住:先让功能跑起来,再优化性能。使用DevTools找到真正的性能瓶颈,针对性优化。
相关资源
💡 小贴士: Flutter的热重载功能可以极大提升开发效率,善用
r命令进行热重载,R命令进行热重启!
关注我,获取更多Flutter干货! 🚀