Skip to content

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 与其他框架的比较

特性FlutterReact NativeXamarin
语言DartJavaScriptC#
UI 渲染自绘引擎原生组件原生组件
性能优秀良好良好
热重载支持支持部分支持
社区支持快速增长成熟稳定
学习曲线中等中等中等

环境搭建

安装 Flutter SDK

Windows

  1. 下载 Flutter SDK
  2. 解压到所需位置(如 C:\src\flutter
  3. 更新环境变量,添加 flutter\bin 到 Path
  4. 运行 flutter doctor 检查安装状态

macOS

bash
# 使用 Homebrew 安装
brew install --cask flutter

# 或手动安装
git clone https://github.com/flutter/flutter.git
export PATH="$PATH:`pwd`/flutter/bin"
flutter doctor

Linux

bash
# 下载 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 项目

bash
# 命令行创建
flutter create my_first_app

# 进入项目目录
cd my_first_app

# 运行项目
flutter run

或通过 IDE 创建:

  1. 打开 VS Code 或 Android Studio
  2. 选择 "Create New Flutter Project"
  3. 按照向导完成项目创建

Dart 语言基础

Flutter 使用 Dart 作为开发语言,了解 Dart 基础对 Flutter 开发至关重要。

变量和数据类型

dart
// 变量声明
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
};

函数

dart
// 基本函数
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)}');
}

类和对象

dart
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');
  }
}

异步编程

dart
// 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 基础组件

基本布局组件

dart
// 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 组件

dart
// 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}');
      },
    );
  },
)

导航和路由

dart
// 基本导航
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

dart
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 状态管理

dart
// 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     # 项目配置和依赖

主应用结构

dart
// 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(),
        },
      ),
    );
  }
}

网络请求和数据处理

dart
// 使用 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());
        },
      ),
    );
  }
}

本地存储

dart
// 使用 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']),
  );
}

动画

dart
// 基本动画
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 最佳实践

代码组织

  1. 遵循单一职责原则:每个类和函数应该只有一个职责
  2. 使用适当的文件夹结构:按功能或特性组织代码
  3. 将业务逻辑与 UI 分离:使用 Provider、Bloc 或其他状态管理方案
  4. 创建可复用组件:将常用 UI 元素抽象为独立组件

性能优化

  1. 使用 const 构造函数:减少重建开销

    dart
    const MyWidget(key: Key('my-widget'))
  2. 避免不必要的 setState:只在需要更新 UI 时调用

  3. 使用 ListView.builder:处理长列表时按需构建项目

  4. 图片优化:使用适当的缓存和占位符

    dart
    CachedNetworkImage(
      imageUrl: 'https://example.com/image.jpg',
      placeholder: (context, url) => CircularProgressIndicator(),
      errorWidget: (context, url, error) => Icon(Icons.error),
    )
  5. 延迟加载:使用 FutureBuilder 或 StreamBuilder 延迟加载数据

响应式设计

  1. 使用 MediaQuery:获取屏幕尺寸和方向

    dart
    final screenWidth = MediaQuery.of(context).size.width;
    final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
  2. 使用 LayoutBuilder:根据父组件约束调整布局

    dart
    LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          return WideLayout();
        } else {
          return NarrowLayout();
        }
      },
    )
  3. 使用 Flexible 和 Expanded:创建自适应布局

    dart
    Row(
      children: [
        Expanded(flex: 2, child: LeftPanel()),
        Expanded(flex: 3, child: RightPanel()),
      ],
    )

测试

  1. 单元测试:测试独立功能和业务逻辑

    dart
    void main() {
      test('Counter increments smoke test', () {
        final counter = Counter();
        counter.increment();
        expect(counter.value, 1);
      });
    }
  2. Widget 测试:测试 UI 组件

    dart
    void 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);
      });
    }
  3. 集成测试:测试整个应用流程

    dart
    void 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 的基础知识、核心组件、状态管理、网络请求、本地存储和最佳实践等内容,希望能帮助你开始 Flutter 开发之旅。

随着你的深入学习,你将能够构建更复杂、更精美的应用程序。Flutter 社区非常活跃,有大量的资源和库可供使用,不断学习和实践是掌握 Flutter 的关键。

祝你在 Flutter 开发中取得成功!


如有问题或建议,请通过 GitHub Issues 反馈。

Flutter 基础组件

基本布局组件

dart
// 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 组件

dart
// 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}');
      },
    );
  },
)

导航和路由

dart
// 基本导航
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

dart
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 状态管理

dart
// 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     # 项目配置和依赖

主应用结构

dart
// 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(),
        },
      ),
    );
  }
}

网络请求和数据处理

dart
// 使用 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());
        },
      ),
    );
  }
}

本地存储

dart
// 使用 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']),
  );
}

动画

dart
// 基本动画
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 最佳实践

代码组织

  1. 遵循单一职责原则:每个类和函数应该只有一个职责
  2. 使用适当的文件夹结构:按功能或特性组织代码
  3. 将业务逻辑与 UI 分离:使用 Provider、Bloc 或其他状态管理方案
  4. 创建可复用组件:将常用 UI 元素抽象为独立组件

性能优化

  1. 使用 const 构造函数:减少重建开销

    dart
    const MyWidget(key: Key('my-widget'))
  2. 避免不必要的 setState:只在需要更新 UI 时调用

  3. 使用 ListView.builder:处理长列表时按需构建项目

  4. 图片优化:使用适当的缓存和占位符

    dart
    CachedNetworkImage(
      imageUrl: 'https://example.com/image.jpg',
      placeholder: (context, url) => CircularProgressIndicator(),
      errorWidget: (context, url, error) => Icon(Icons.error),
    )
  5. 延迟加载:使用 FutureBuilder 或 StreamBuilder 延迟加载数据

响应式设计

  1. 使用 MediaQuery:获取屏幕尺寸和方向

    dart
    final screenWidth = MediaQuery.of(context).size.width;
    final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
  2. 使用 LayoutBuilder:根据父组件约束调整布局

    dart
    LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          return WideLayout();
        } else {
          return NarrowLayout();
        }
      },
    )
  3. 使用 Flexible 和 Expanded:创建自适应布局

    dart
    Row(
      children: [
        Expanded(flex: 2, child: LeftPanel()),
        Expanded(flex: 3, child: RightPanel()),
      ],
    )

测试

  1. 单元测试:测试独立功能和业务逻辑

    dart
    void main() {
      test('Counter increments smoke test', () {
        final counter = Counter();
        counter.increment();
        expect(counter.value, 1);
      });
    }
  2. Widget 测试:测试 UI 组件

    dart
    void 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);
      });
    }
  3. 集成测试:测试整个应用流程

    dart
    void 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 与其他框架的比较

特性FlutterReact NativeXamarin
语言DartJavaScriptC#
UI 渲染自绘引擎原生组件原生组件
性能优秀良好良好
热重载支持支持部分支持
社区支持快速增长成熟稳定
学习曲线中等中等中等

环境搭建

安装 Flutter SDK

Windows

  1. 下载 Flutter SDK
  2. 解压到所需位置(如 C:\src\flutter
  3. 更新环境变量,添加 flutter\bin 到 Path
  4. 运行 flutter doctor 检查安装状态

macOS

bash
# 使用 Homebrew 安装
brew install --cask flutter

# 或手动安装
git clone https://github.com/flutter/flutter.git
export PATH="$PATH:`pwd`/flutter/bin"
flutter doctor

Linux

bash
# 下载 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 项目

bash
# 命令行创建
flutter create my_first_app

# 进入项目目录
cd my_first_app

# 运行项目
flutter run

或通过 IDE 创建:

  1. 打开 VS Code 或 Android Studio
  2. 选择 "Create New Flutter Project"
  3. 按照向导完成项目创建

Dart 语言基础

Flutter 使用 Dart 作为开发语言,了解 Dart 基础对 Flutter 开发至关重要。

变量和数据类型

dart
// 变量声明
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
};

函数

dart
// 基本函数
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)}');
}

类和对象

dart
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');
  }
}

异步编程

dart
// 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();
}

vitepress开发指南