Skip to content

第一个应用

从零开始,手把手构建一个简单的 Flutter 计数器应用。

创建项目

bash
flutter create counter_app
cd counter_app

项目结构

创建完成后,项目结构如下:

counter_app/
├── lib/                    # ⭐ Dart 源码(你 90% 的时间都在这里)
│   └── main.dart           #   应用入口
├── pubspec.yaml            # ⭐ 项目配置(依赖、资源、元数据)
├── analysis_options.yaml   #   代码规范配置
├── test/                   #   测试文件
├── android/                #   Android 原生工程(很少直接改)
├── ios/                    #   iOS 原生工程(很少直接改)
├── web/                    #   Web 平台文件
├── ohos/                   #   鸿蒙平台文件(需 flutter-ohos 定制版)
└── README.md

初学者关注点

  • 现在就搞懂lib/pubspec.yamlmain.dart
  • 用到再看android/ios/ 等原生目录(需要写插件或改原生配置时才动)
  • 暂时忽略test/(先学会写功能,再学测试)

三个核心文件

1. pubspec.yaml — 项目配置

这是 Flutter 项目的"身份证",定义了项目是谁、依赖什么、包含哪些资源。

yaml
name: my_app               # 包名(唯一标识)
description: My Flutter App # 描述
version: 1.0.0+1           # 版本号+构建号(1.0.0 是版本,1 是构建号)

environment:
  sdk: ^3.6.0              # Dart SDK 版本约束

dependencies:              # 运行时依赖(打包进 APP 的包)
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8  # iOS 风格图标
  http: ^1.2.0             # 第三方包示例

dev_dependencies:          # 开发依赖(只在开发时用,不打包进 APP)
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

flutter:                   # Flutter 专属配置
  uses-material-design: true  # 使用 Material 图标
  assets:                     # 声明静态资源(必须声明才能使用)
    - images/
    - assets/data.json
  fonts:                    # 自定义字体
    - family: MyFont
      fonts:
        - asset: fonts/MyFont-Regular.ttf
        - asset: fonts/MyFont-Bold.ttf
          weight: 700

常用操作:

操作命令
安装/更新依赖flutter pub get
添加第三方包flutter pub add http
移除第三方包flutter pub remove http
查看过期依赖flutter pub outdated
升级依赖flutter pub upgrade

注意

每次修改 pubspec.yaml 后,必须运行 flutter pub get 安装依赖,否则 IDE 会报错。

2. main.dart — 应用入口

Flutter 应用的启动文件,main() 函数是程序执行的起点:

dart
import 'package:flutter/material.dart';

void main() {
  // ─── ★ runApp 启动应用 ──────────────
  runApp(const MyApp());
  // ─── ☆ runApp 启动应用 ──────────────
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    // ─── ★ MaterialApp 应用外壳 ──────────────
    return MaterialApp(
      title: 'My App',
      home: Scaffold(             // 页面骨架(提供 AppBar、Body 等)
        appBar: AppBar(title: const Text('首页')),
        body: const Center(child: Text('Hello Flutter!')),
      ),
    );
    // ─── ☆ MaterialApp 应用外壳 ──────────────
  }
}

关键理解:

概念说明
main()程序入口,调用 runApp() 启动应用
runApp()接收一个 Widget 作为根组件,挂载到屏幕
MaterialAppMaterial Design 风格的应用外壳,配置主题、路由等
Scaffold页面骨架,提供 AppBar、Body、FloatingActionButton 等区域

3. analysis_options.yaml — 代码规范

Dart 的静态代码分析配置,相当于"拼写检查器"——在你写代码时实时提示问题。

yaml
# 引入 Flutter 官方推荐规则集
include: package:flutter_lints/flutter.yaml

# 自定义规则
linter:
  rules:
    prefer_const_constructors: true   # 优先用 const 构造函数
    prefer_single_quotes: true        # 优先用单引号
    avoid_print: true                 # 避免用 print

# 排除不需要分析的文件
analyzer:
  exclude:
    - "**/*.g.dart"        # 代码生成的文件
    - "**/*.freezed.dart"

# 第三方 lint 插件(如使用 Riverpod 时添加)
# plugins:
#   riverpod_lint: 3.1.3

验证是否生效:

bash
flutter analyze    # 扫描整个项目,报告所有问题

IDE 会自动读取此文件,在编写代码时实时标黄/标红提示问题。修改配置后如果未生效,运行 flutter pub get 或重启 IDE。

lib/ 目录 — 你的主战场

lib/ 是你编写 Dart 代码的地方。项目初期只有一个 main.dart,随着功能增长需要合理组织:

lib/
├── main.dart                # 入口:只做初始化和启动
├── app.dart                 # 根 Widget(MaterialApp)
├── pages/                   # 页面
│   ├── home_page.dart
│   └── login_page.dart
├── widgets/                 # 可复用组件
│   └── loading.dart
├── models/                  # 数据模型
│   └── user.dart
├── services/                # 服务(网络请求、本地存储等)
│   └── api_service.dart
└── utils/                   # 工具函数
    └── validators.dart

组织原则

  • 入口简洁main.dart 只负责启动,不超过 20 行
  • 按功能分层:页面、组件、模型、服务各归其位
  • 适度拆分:至少 2-3 处复用时才提取公共组件,避免过度封装

编写代码

打开 lib/main.dart,替换为以下内容:

dart
import 'package:flutter/material.dart';

void main() {
  runApp(const CounterApp());
}

class CounterApp extends StatelessWidget {
  const CounterApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '计数器',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const CounterPage(),
    );
  }
}

// ─── ★ StatefulWidget 可变组件 ──────────────
class CounterPage extends StatefulWidget {
  const CounterPage({super.key});

  @override
  State<CounterPage> createState() => _CounterPageState();
}
// ─── ☆ StatefulWidget 可变组件 ──────────────

class _CounterPageState extends State<CounterPage> {
  // ─── ★ 可变状态变量 ──────────────
  int _counter = 0;
  // ─── ☆ 可变状态变量 ──────────────

  void _increment() {
    // ─── ★ setState 触发 UI 重建 ──────────────
    setState(() {
      _counter++;
    });
    // ─── ☆ setState 触发 UI 重建 ──────────────
  }

  void _decrement() {
    setState(() {
      _counter--;
    });
  }

  void _reset() {
    setState(() {
      _counter = 0;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter 计数器'),
        centerTitle: true,
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '当前计数',
              style: TextStyle(fontSize: 20, color: Colors.grey),
            ),
            const SizedBox(height: 8),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.displayLarge?.copyWith(
                    color: _counter > 0
                        ? Colors.green
                        : _counter < 0
                            ? Colors.red
                            : Colors.grey,
                  ),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'add',
            onPressed: _increment,
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            heroTag: 'sub',
            onPressed: _decrement,
            child: const Icon(Icons.remove),
          ),
          const SizedBox(height: 10),
          FloatingActionButton.small(
            heroTag: 'reset',
            onPressed: _reset,
            child: const Icon(Icons.refresh),
          ),
        ],
      ),
    );
  }
}

运行项目

bash
# 确保设备已连接或模拟器已启动
flutter devices

# 运行
flutter run

热重载 vs 热重启

操作快捷键说明
热重载r秒级生效,保持状态,修改 UI 时使用
热重启R完全重启,状态重置,修改初始化逻辑时使用
退出运行q停止应用

代码解读

应用结构

CounterApp (StatelessWidget)
└── MaterialApp
    └── CounterPage (StatefulWidget)
        └── Scaffold
            ├── AppBar        ← 顶部导航栏
            ├── Center        ← 内容区域(居中)
            │   └── Column    ← 垂直排列的列
            │       ├── Text  ← "当前计数"
            │       └── Text  ← 计数值
            └── FloatingActionButton ← 悬浮按钮

关键概念

概念说明
StatelessWidget不可变组件,CounterApp 本身不需要状态
StatefulWidget可变组件,CounterPage 管理计数状态
setState()通知框架状态已变更,触发 UI 重建
ScaffoldMaterial Design 页面骨架,提供 AppBar、Body、FAB 等
build()描述 UI 的方法,每次状态变更都会重新调用
Theme.of(context)获取当前主题,实现样式复用

状态变化流程

用户点击 → _increment() → setState() → build() 重新执行 → UI 更新

常用命令速查

flutter create — 创建项目

bash
flutter create <项目目>

常用参数:

参数说明
--project-name <名称>指定项目名称(pubspec.yaml 中的 name),必须全小写,可用下划线分隔。若不指定则默认使用目录名
--platforms <平台列表>指定支持的平台,逗号分隔。可选:androidioswebohosmacoswindowslinux
--org <组织标识>指定组织标识(反向域名格式),用于生成包名。如 --org com.example,则包名为 com.example.<项目名>
-t <模板>项目模板类型:app(默认)、module(模块)、plugin(插件)、plugin_ffi(FFI 插件)

常见示例:

bash
# 指定项目名称和支持平台
flutter create --project-name hongmeng --platforms android,ios,web,ohos hongmeng
  • --project-name hongmeng:项目名设为 hongmeng
  • --platforms android,ios,web,ohos:生成 Android、iOS、Web、鸿蒙四个平台目录
  • 最后的 hongmeng:创建 hongmeng/ 目录
bash
# 指定组织标识,自定义包名
flutter create --platforms android,ios,ohos,web --org com.mawanli learn
  • --org com.mawanli:包名变为 com.mawanli.learn
  • learn:项目目录名,同时作为默认项目名
bash
# 在当前目录创建项目(适合已有空目录)
flutter create --platforms android,ios,ohos,web --org com.mawanli --project-name learn .
  • --project-name learn:显式指定项目名(覆盖从目录名推断的默认值)
  • .:在当前目录创建,不新建子目录

flutter run — 运行项目

bash
flutter run                          # 在默认设备运行
flutter run -d <设备ID>              # 在指定设备运行

常用参数:

参数说明
-d <设备ID>指定目标设备。常用值:chromeweb-serverohos、设备序列号。通过 flutter devices 查看可用设备
--web-port <端口>Web 服务器监听端口,仅 -d web-server-d chrome 时有效,默认随机分配
--web-hostname <主机名>Web 服务器监听主机名,默认 localhost(仅本机访问)。设为 0.0.0.0 允许局域网访问

常见示例:

bash
# Web 服务器模式运行,指定端口
flutter run -d web-server --web-port=8080
  • -d web-server:不自动打开浏览器,终端输出访问 URL,适合远程开发环境
  • --web-port=8080:访问地址为 http://localhost:8080
bash
# 允许局域网访问的 Web 运行
flutter run -d web-server --web-hostname=0.0.0.0 --web-port=8080
  • --web-hostname=0.0.0.0:监听所有网络接口,局域网设备可通过 http://<本机IP>:8080 访问
  • 适合在手机上预览 Web 版效果

web-server vs chrome

  • -d web-server:不自动打开浏览器,适合远程开发、CI/CD
  • -d chrome:自动打开 Chrome 并支持 DevTools 调试,适合日常开发

其他日常命令

命令说明
flutter devices查看可用设备
r热重载(运行中按,秒级生效)
R热重启(运行中按,完全重启)
q退出运行

依赖管理

命令说明
flutter pub get安装依赖
flutter pub add 包名添加依赖包
flutter pub remove 包名移除依赖包
flutter pub outdated查看过期依赖
flutter pub upgrade升级依赖

构建与检查

命令说明
flutter build apk构建 Android APK
flutter build ios构建 iOS(需 macOS)
flutter build web构建 Web
flutter build ohos构建鸿蒙应用(需 flutter-ohos 定制版)
flutter clean清理构建产物
flutter analyze静态代码分析
flutter test运行测试

下一步

基于 Flutter 官方文档整理