02-Flutter进阶开发

摘要:本文是Flutter进阶开发指南,深入讲解路由与导航、网络请求、动画系统、自定义组件、主题与国际化等核心主题。通过本文档,你将掌握Flutter开发的中高级技能,能够构建复杂的商业应用。

关键词:Flutter、路由导航、网络请求、动画、自定义组件、主题、国际化、HTTP

适合人群:已掌握Flutter基础的开发者、想深入学习进阶技能的工程师

阅读时间:约 50 分钟

版本信息:Flutter 3.x | Dart 3.x | 支持 Android/iOS/Web

更新日期:2026-09-03


文章目录

  • [1. 路由与导航](#1. 路由与导航)
    • [1.1 基础路由](#1.1 基础路由)
    • [1.2 命名路由](#1.2 命名路由)
    • [1.3 路由传参](#1.3 路由传参)
    • [1.4 路由返回数据](#1.4 路由返回数据)
  • [2. 网络请求](#2. 网络请求)
    • [2.1 http 包基础用法](#2.1 http 包基础用法)
    • [2.2 GET 请求](#2.2 GET 请求)
    • [2.3 POST 请求](#2.3 POST 请求)
    • [2.4 错误处理](#2.4 错误处理)
  • [3. 动画系统](#3. 动画系统)
  • [4. 自定义组件](#4. 自定义组件)
    • [4.1 组合现有组件](#4.1 组合现有组件)
    • [4.2 CustomPaint 自定义绘制](#4.2 CustomPaint 自定义绘制)
  • [5. 主题与样式](#5. 主题与样式)
    • [5.1 ThemeData 配置](#5.1 ThemeData 配置)
    • [5.2 动态主题切换](#5.2 动态主题切换)
  • [6. 国际化](#6. 国际化)
    • [6.1 配置国际化](#6.1 配置国际化)
    • [6.2 使用多语言](#6.2 使用多语言)
  • [💡 综合实战案例](#💡 综合实战案例)
  • [❓ 常见问题 FAQ](#❓ 常见问题 FAQ)
    • [1. 如何在页面跳转时传递复杂对象?](#1. 如何在页面跳转时传递复杂对象?)
    • [2. 网络请求失败如何重试?](#2. 网络请求失败如何重试?)
    • [3. 如何优化列表滚动性能?](#3. 如何优化列表滚动性能?)
    • [4. 动画卡顿如何优化?](#4. 动画卡顿如何优化?)
    • [5. 如何全局管理用户登录状态?](#5. 如何全局管理用户登录状态?)
  • [📝 总结](#📝 总结)
  • [📚 参考资料](#📚 参考资料)

1. 路由与导航

1.1 基础路由

  Flutter 使用 Navigator 管理页面跳转。

dart 复制代码
// 跳转到新页面
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const DetailPage()),
);

// 返回上一页
Navigator.pop(context);

  效果说明

  • Navigator.push 将新页面压入导航栈
  • MaterialPageRoute 提供平台特定的页面过渡动画
  • Navigator.pop 弹出当前页面,返回上一页

1.2 命名路由

  通过路由名称跳转,便于统一管理。

dart 复制代码
// 在 MaterialApp 中配置路由
MaterialApp(
  initialRoute: '/',
  routes: {
    '/': (context) => const HomePage(),
    '/detail': (context) => const DetailPage(),
    '/settings': (context) => const SettingsPage(),
  },
);

// 使用命名路由跳转
Navigator.pushNamed(context, '/detail');

// 返回上一页
Navigator.pop(context);

  效果说明

  • routes 定义路由映射表
  • pushNamed 通过路由名称跳转
  • 适合中大型项目,便于路由管理

1.3 路由传参

  跳转时传递数据到新页面。

dart 复制代码
// 方式一:构造函数传参
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(itemId: 123, title: '商品详情'),
  ),
);

// 接收参数
class DetailPage extends StatelessWidget {
  final int itemId;
  final String title;

  const DetailPage({
    super.key,
    required this.itemId,
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Text('商品ID: $itemId'),
    );
  }
}

  效果说明

  • 通过构造函数传递参数是最常用的方式
  • 使用 required 标记必填参数
  • 参数类型可以是任意类型(int、String、对象等)

1.4 路由返回数据

  从新页面返回数据到上一页。

dart 复制代码
// 上一页:等待返回结果
void _navigateAndGetValue() async {
  final result = await Navigator.push(
    context,
    MaterialPageRoute(builder: (context) => const InputPage()),
  );
  
  if (result != null) {
    print('返回值: $result');
  }
}

// 新页面:返回数据
class InputPage extends StatelessWidget {
  const InputPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('输入页面')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context, '返回的数据');
          },
          child: const Text('返回'),
        ),
      ),
    );
  }
}

  效果说明

  • Navigator.pop(context, data) 第二个参数为返回值
  • 使用 await 等待页面返回
  • 返回值可以是任意类型

2. 网络请求

2.1 http 包基础用法

  Flutter 官方推荐使用 http 包进行网络请求。

  安装依赖

  在 pubspec.yaml 中添加:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  http: ^1.1.0

  运行命令:

bash 复制代码
flutter pub get

2.2 GET 请求

dart 复制代码
import 'package:http/http.dart' as http;
import 'dart:convert';

class ApiService {
  static const String baseUrl = 'https://jsonplaceholder.typicode.com';

  // 获取用户列表
  static Future<List<dynamic>> getUsers() async {
    final response = await http.get(Uri.parse('$baseUrl/users'));

    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('获取用户列表失败');
    }
  }

  // 获取单个用户
  static Future<Map<String, dynamic>> getUser(int id) async {
    final response = await http.get(Uri.parse('$baseUrl/users/$id'));

    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('获取用户失败');
    }
  }
}

  效果说明

  • http.get 发送 GET 请求
  • response.statusCode 检查响应状态码
  • json.decode 将 JSON 字符串解析为 Dart 对象
  • 200 表示请求成功,其他状态码表示失败

2.3 POST 请求

dart 复制代码
class ApiService {
  // 创建用户
  static Future<Map<String, dynamic>> createUser(Map<String, dynamic> data) async {
    final response = await http.post(
      Uri.parse('$baseUrl/users'),
      headers: {'Content-Type': 'application/json'},
      body: json.encode(data),
    );

    if (response.statusCode == 201) {
      return json.decode(response.body);
    } else {
      throw Exception('创建用户失败');
    }
  }

  // 更新用户
  static Future<Map<String, dynamic>> updateUser(int id, Map<String, dynamic> data) async {
    final response = await http.put(
      Uri.parse('$baseUrl/users/$id'),
      headers: {'Content-Type': 'application/json'},
      body: json.encode(data),
    );

    if (response.statusCode == 200) {
      return json.decode(response.body);
    } else {
      throw Exception('更新用户失败');
    }
  }

  // 删除用户
  static Future<void> deleteUser(int id) async {
    final response = await http.delete(Uri.parse('$baseUrl/users/$id'));

    if (response.statusCode != 200) {
      throw Exception('删除用户失败');
    }
  }
}

  效果说明

  • http.post 发送 POST 请求
  • headers 设置请求头,Content-Type: application/json 表示 JSON 格式
  • json.encode 将 Dart 对象编码为 JSON 字符串
  • 201 表示创建成功,200 表示更新成功

2.4 错误处理

  完整的网络请求应包含错误处理。

dart 复制代码
class ApiService {
  static Future<List<dynamic>> fetchUsers() async {
    try {
      final response = await http.get(
        Uri.parse('$baseUrl/users'),
      ).timeout(const Duration(seconds: 10));

      if (response.statusCode == 200) {
        return json.decode(response.body);
      } else {
        throw Exception('请求失败: ${response.statusCode}');
      }
    } on http.ClientException catch (e) {
      throw Exception('网络错误: $e');
    } on TimeoutException catch (e) {
      throw Exception('请求超时: $e');
    } catch (e) {
      throw Exception('未知错误: $e');
    }
  }
}

  效果说明

  • timeout 设置请求超时时间
  • try-catch 捕获不同类型的异常
  • ClientException 网络错误
  • TimeoutException 超时错误

3. 动画系统

3.1 隐式动画

  Flutter 提供了一系列隐式动画组件,只需改变属性值即可自动播放动画。

AnimatedContainer

dart 复制代码
class AnimatedContainerExample extends StatefulWidget {
  const AnimatedContainerExample({super.key});

  @override
  State<AnimatedContainerExample> createState() => _AnimatedContainerExampleState();
}

class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
  bool _expanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          _expanded = !_expanded;
        });
      },
      child: Center(
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeInOut,
          width: _expanded ? 200.0 : 100.0,
          height: _expanded ? 200.0 : 100.0,
          color: _expanded ? Colors.blue : Colors.red,
          alignment: _expanded ? Alignment.center : Alignment.topLeft,
          child: const Text('点击我'),
        ),
      ),
    );
  }
}

  效果说明

  • duration 动画持续时间
  • curve 动画曲线(缓入缓出、线性等)
  • 属性值改变时自动播放过渡动画
  • 支持 width、height、color、alignment 等属性

AnimatedOpacity

dart 复制代码
AnimatedOpacity(
  opacity: _visible ? 1.0 : 0.0,
  duration: const Duration(milliseconds: 500),
  child: const Text('淡入淡出'),
)

AnimatedPositioned

dart 复制代码
Stack(
  children: [
    AnimatedPositioned(
      duration: const Duration(milliseconds: 300),
      top: _moved ? 100.0 : 50.0,
      left: _moved ? 100.0 : 50.0,
      child: const Text('移动动画'),
    ),
  ],
)

3.2 显式动画

  需要更精细控制时使用显式动画。

dart 复制代码
class ExplicitAnimationExample extends StatefulWidget {
  const ExplicitAnimationExample({super.key});

  @override
  State<ExplicitAnimationExample> createState() => _ExplicitAnimationExampleState();
}

class _ExplicitAnimationExampleState extends State<ExplicitAnimationExample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    );

    _animation = Tween<double>(
      begin: 0.0,
      end: 300.0,
    ).animate(CurvedAnimation(
      parent: _controller,
      curve: Curves.easeInOut,
    ));
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return Container(
              width: _animation.value,
              height: 100,
              color: Colors.blue,
            );
          },
        ),
        ElevatedButton(
          onPressed: () {
            if (_controller.isCompleted) {
              _controller.reverse();
            } else {
              _controller.forward();
            }
          },
          child: const Text('播放动画'),
        ),
      ],
    );
  }
}

  效果说明

  • AnimationController 控制动画播放
  • Tween 定义动画值的变化范围
  • CurvedAnimation 添加动画曲线
  • AnimatedBuilder 监听动画值变化并重建 UI
  • forward() 正向播放,reverse() 反向播放

3.3 Hero 动画

  Hero 动画实现页面间组件的平滑过渡。

dart 复制代码
// 第一页
class FirstPage extends StatelessWidget {
  const FirstPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => const SecondPage()),
          );
        },
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://example.com/image.jpg',
            width: 100,
            height: 100,
          ),
        ),
      ),
    );
  }
}

// 第二页
class SecondPage extends StatelessWidget {
  const SecondPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://example.com/image.jpg',
            width: 300,
            height: 300,
          ),
        ),
      ),
    );
  }
}

  效果说明

  • Hero 组件的 tag 必须相同
  • 跳转时图片会从小图平滑过渡到大图
  • 适合图片预览、卡片展开等场景

4. 自定义组件

4.1 组合现有组件

  通过组合现有组件创建可复用的自定义组件。

dart 复制代码
class CustomCard extends StatelessWidget {
  final String title;
  final String description;
  final VoidCallback onTap;

  const CustomCard({
    super.key,
    required this.title,
    required this.description,
    required this.onTap,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: InkWell(
        onTap: onTap,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                title,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 8),
              Text(
                description,
                style: const TextStyle(
                  fontSize: 14,
                  color: Colors.grey,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// 使用自定义组件
CustomCard(
  title: '卡片标题',
  description: '卡片描述内容',
  onTap: () => print('卡片被点击'),
)

  效果说明

  • 使用 final 声明组件属性
  • required 标记必填参数
  • 组合 Card、InkWell、Padding 等现有组件
  • 提供回调函数处理交互

4.2 CustomPaint 自定义绘制

  使用 CustomPaint 绘制自定义图形。

dart 复制代码
class CustomPaintExample extends StatelessWidget {
  const CustomPaintExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: CustomPaint(
        size: const Size(200, 200),
        painter: MyPainter(),
      ),
    );
  }
}

class MyPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.blue
      ..style = PaintingStyle.fill;

    // 绘制圆形
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      size.width / 2,
      paint,
    );

    // 绘制矩形
    paint.color = Colors.red;
    canvas.drawRect(
      Rect.fromLTWH(50, 50, 100, 100),
      paint,
    );
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

  效果说明

  • CustomPaint 需要配合 CustomPainter 使用
  • paint 方法中执行绘制操作
  • Canvas 提供绘制 API(drawCircle、drawRect 等)
  • Paint 设置画笔样式(颜色、填充模式等)

5. 主题与样式

5.1 ThemeData 配置

  全局配置应用主题。

dart 复制代码
MaterialApp(
  theme: ThemeData(
    primarySwatch: Colors.blue,
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.blue,
      brightness: Brightness.light,
    ),
    appBarTheme: const AppBarTheme(
      backgroundColor: Colors.blue,
      foregroundColor: Colors.white,
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
      ),
    ),
    textTheme: const TextTheme(
      headlineLarge: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
      bodyLarge: TextStyle(fontSize: 16),
    ),
  ),
  home: const HomePage(),
)

  效果说明

  • primarySwatch 设置主色调
  • colorScheme 定义颜色方案
  • appBarTheme 统一配置 AppBar 样式
  • textTheme 配置全局文字样式

5.2 动态主题切换

  支持亮色/暗色主题切换。

dart 复制代码
class ThemeApp extends StatefulWidget {
  const ThemeApp({super.key});

  @override
  State<ThemeApp> createState() => _ThemeAppState();
}

class _ThemeAppState extends State<ThemeApp> {
  bool _isDarkMode = false;

  void _toggleTheme() {
    setState(() {
      _isDarkMode = !_isDarkMode;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: _isDarkMode ? ThemeData.dark() : ThemeData.light(),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('主题切换'),
          actions: [
            IconButton(
              icon: Icon(_isDarkMode ? Icons.light_mode : Icons.dark_mode),
              onPressed: _toggleTheme,
            ),
          ],
        ),
        body: const Center(child: Text('点击图标切换主题')),
      ),
    );
  }
}

  效果说明

  • ThemeData.dark()ThemeData.light() 提供预设主题
  • 通过状态管理切换主题
  • 切换后整个应用的主题会自动更新

6. 国际化

6.1 配置国际化

  添加依赖

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

  配置 MaterialApp

dart 复制代码
MaterialApp(
  supportedLocales: const [
    Locale('en', 'US'),
    Locale('zh', 'CN'),
  ],
  localizationsDelegates: const [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  locale: const Locale('zh', 'CN'),
  home: const HomePage(),
)

6.2 使用多语言

  创建语言文件

dart 复制代码
// lib/l10n/app_localizations.dart
class AppLocalizations {
  final Locale locale;

  AppLocalizations(this.locale);

  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
  }

  Map<String, String> get _localizedValues {
    return {
      'en_US': {
        'title': 'Home',
        'welcome': 'Welcome',
      },
      'zh_CN': {
        'title': '首页',
        'welcome': '欢迎',
      },
    }[locale.toString()]!;
  }

  String get title => _localizedValues['title']!;
  String get welcome => _localizedValues['welcome']!;
}

  使用多语言

dart 复制代码
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    final localizations = AppLocalizations.of(context);
    
    return Scaffold(
      appBar: AppBar(title: Text(localizations.title)),
      body: Center(child: Text(localizations.welcome)),
    );
  }
}

  效果说明

  • supportedLocales 定义支持的语言列表
  • localizationsDelegates 提供本地化资源
  • 通过 AppLocalizations.of(context) 获取当前语言的文字

💡 综合实战案例

新闻列表应用

  结合路由、网络请求、动画等知识,实现一个完整的新闻列表应用。

dart 复制代码
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '新闻列表',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const NewsListPage(),
    );
  }
}

class NewsItem {
  final String title;
  final String description;
  final String imageUrl;
  final String url;

  NewsItem({
    required this.title,
    required this.description,
    required this.imageUrl,
    required this.url,
  });

  factory NewsItem.fromJson(Map<String, dynamic> json) {
    return NewsItem(
      title: json['title'] ?? '',
      description: json['description'] ?? '',
      imageUrl: json['urlToImage'] ?? '',
      url: json['url'] ?? '',
    );
  }
}

class NewsListPage extends StatefulWidget {
  const NewsListPage({super.key});

  @override
  State<NewsListPage> createState() => _NewsListPageState();
}

class _NewsListPageState extends State<NewsListPage> {
  List<NewsItem> _newsList = [];
  bool _isLoading = true;
  String _errorMessage = '';

  @override
  void initState() {
    super.initState();
    _fetchNews();
  }

  Future<void> _fetchNews() async {
    try {
      setState(() {
        _isLoading = true;
        _errorMessage = '';
      });

      final response = await http.get(
        Uri.parse('https://newsapi.org/v2/top-headlines?country=cn&apiKey=YOUR_API_KEY'),
      ).timeout(const Duration(seconds: 10));

      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        final List<dynamic> articles = data['articles'] ?? [];
        
        setState(() {
          _newsList = articles
              .map((json) => NewsItem.fromJson(json))
              .where((item) => item.title.isNotEmpty)
              .toList();
          _isLoading = false;
        });
      } else {
        setState(() {
          _errorMessage = '加载失败: ${response.statusCode}';
          _isLoading = false;
        });
      }
    } catch (e) {
      setState(() {
        _errorMessage = '网络错误: $e';
        _isLoading = false;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('新闻列表'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _fetchNews,
          ),
        ],
      ),
      body: _isLoading
          ? const Center(child: CircularProgressIndicator())
          : _errorMessage.isNotEmpty
              ? Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      Text(_errorMessage),
                      const SizedBox(height: 16),
                      ElevatedButton(
                        onPressed: _fetchNews,
                        child: const Text('重试'),
                      ),
                    ],
                  ),
                )
              : RefreshIndicator(
                  onRefresh: _fetchNews,
                  child: ListView.builder(
                    itemCount: _newsList.length,
                    itemBuilder: (context, index) {
                      final news = _newsList[index];
                      return NewsCard(news: news);
                    },
                  ),
                ),
    );
  }
}

class NewsCard extends StatelessWidget {
  final NewsItem news;

  const NewsCard({super.key, required this.news});

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: InkWell(
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => NewsDetailPage(news: news),
            ),
          );
        },
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (news.imageUrl.isNotEmpty)
              Image.network(
                news.imageUrl,
                width: double.infinity,
                height: 200,
                fit: BoxFit.cover,
                errorBuilder: (context, error, stackTrace) {
                  return Container(
                    height: 200,
                    color: Colors.grey[300],
                    child: const Icon(Icons.broken_image, size: 50),
                  );
                },
              ),
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    news.title,
                    style: const TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    news.description,
                    style: const TextStyle(fontSize: 14, color: Colors.grey),
                    maxLines: 3,
                    overflow: TextOverflow.ellipsis,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class NewsDetailPage extends StatelessWidget {
  final NewsItem news;

  const NewsDetailPage({super.key, required this.news});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('新闻详情')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (news.imageUrl.isNotEmpty)
              Hero(
                tag: news.imageUrl,
                child: Image.network(
                  news.imageUrl,
                  width: double.infinity,
                  height: 250,
                  fit: BoxFit.cover,
                ),
              ),
            const SizedBox(height: 16),
            Text(
              news.title,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Text(
              news.description,
              style: const TextStyle(fontSize: 16),
            ),
          ],
        ),
      ),
    );
  }
}

  效果说明

  • 使用 http 包获取新闻数据
  • 使用 ListView.builder 展示新闻列表
  • 使用 RefreshIndicator 实现下拉刷新
  • 使用 Hero 动画实现图片平滑过渡
  • 包含完整的错误处理和加载状态

❓ 常见问题 FAQ

1. 如何在页面跳转时传递复杂对象?

  解答

  直接传递对象实例即可:

dart 复制代码
class User {
  final String name;
  final int age;
  User(this.name, this.age);
}

// 跳转
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => DetailPage(user: user),
  ),
);

// 接收
class DetailPage extends StatelessWidget {
  final User user;
  const DetailPage({super.key, required this.user});
}

2. 网络请求失败如何重试?

  解答

  使用 try-catch 和循环实现重试:

dart 复制代码
Future<T> retryRequest<T>(
  Future<T> Function() request,
  int maxRetries,
) async {
  for (int i = 0; i < maxRetries; i++) {
    try {
      return await request();
    } catch (e) {
      if (i == maxRetries - 1) rethrow;
      await Future.delayed(Duration(seconds: i + 1));
    }
  }
  throw Exception('重试失败');
}

3. 如何优化列表滚动性能?

  解答

  • 使用 ListView.builder 代替 ListView
  • 图片使用 cacheWidthcacheHeight 限制尺寸
  • 避免在 itemBuilder 中创建复杂组件
dart 复制代码
Image.network(
  url,
  cacheWidth: 400,
  cacheHeight: 300,
)

4. 动画卡顿如何优化?

  解答

  • 使用 const 组件减少重建
  • 避免在 build 方法中执行耗时操作
  • 使用 RepaintBoundary 隔离重绘区域
dart 复制代码
RepaintBoundary(
  child: AnimatedContainer(...),
)

5. 如何全局管理用户登录状态?

  解答

  使用 ValueNotifier 或第三方状态管理库:

dart 复制代码
final userNotifier = ValueNotifier<User?>(null);

// 监听
ValueListenableBuilder<User?>(
  valueListenable: userNotifier,
  builder: (context, user, child) {
    return user != null ? HomePage() : LoginPage();
  },
)

📝 总结

  本文介绍了 Flutter 进阶开发的核心技能:

知识点回顾

模块 核心内容 重要程度
路由导航 push/pop、命名路由、传参 ⭐⭐⭐⭐⭐
网络请求 http、GET/POST、错误处理 ⭐⭐⭐⭐⭐
动画系统 隐式动画、显式动画、Hero ⭐⭐⭐⭐
自定义组件 组合组件、CustomPaint ⭐⭐⭐⭐
主题与国际化 ThemeData、多语言 ⭐⭐⭐

学习建议

  1. 多实践:网络请求和动画需要大量练习
  2. 看源码:学习官方组件的实现方式
  3. 性能优化:关注列表滚动和动画性能
  4. 错误处理:完善的错误处理是商业应用的必备条件

下一步学习

  • 状态管理进阶(Provider、Riverpod、Bloc)
  • 本地存储(SharedPreferences、SQLite)
  • 表单验证
  • 测试与调试

📚 参考资料

相关推荐
a1117762 小时前
莱茵生命终端 网页 html
前端·开源
水月清辉3 小时前
CSS 常用语法详解
前端·css
Eric_见嘉3 小时前
打开这个 VSC 设置「组件引用位置」和数量就都清楚了
前端·typescript·visual studio code
李高钢4 小时前
Python Tornado 框架入门:从零搭建你的第一个异步 Web 应用
前端·python·tornado
宁风4 小时前
JavaScript:函数体系-1
前端·javascript
mmsx4 小时前
Android 手簿 ADB 无线调试全攻略:USB 转 WiFi 一键连接
android·前端
●VON4 小时前
Flutter 鸿蒙插件适配实战:用 is_lock_screen 2.0.0 判断真实锁屏状态
flutter·华为·harmonyos
guwentian4 小时前
Rolldown vs esbuild vs Webpack:Rust 打包器大战我们该怎么选
前端·webpack·rust·esbuild
特立独行的猫A4 小时前
用纯 Rust 重写 Podcastfy:把网页、PDF 和一段文字,变成一档双人对话播客
前端