【Flutter】NewsHub跨平台开发:Flutter适配鸿蒙实战教程

摘要
本文详细介绍如何使用 Flutter 框架开发"NewsHub"新闻聚合应用,实现一套 Dart 代码在 Android、iOS 和 HarmonyOS 三端运行。重点讲解 DevEco Studio 环境配置、华为 HMS 服务集成、响应式 UI 构建、鸿蒙平台专项优化以及 .hap 包打包上架流程。通过本教程,开发者将掌握 Flutter 跨平台开发的完整流程,特别是在鸿蒙生态中的适配技巧。
一、项目架构设计
1.1 整体技术架构
┌─────────────────────────────────────────────────────────────────────┐
│ NewsHub 新闻聚合应用 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Android │ │ iOS │ │ HarmonyOS │ │
│ │ 原生层 │ │ 原生层 │ │ 原生层 │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┴────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ Flutter Engine │ │
│ │ (Dart 代码) │ │
│ └───────┬────────┘ │
│ │ │
│ ┌───────────────────────▼─────────────────────────────────┐ │
│ │ Flutter 业务层 │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ BLoC 状态管理 │ Repository │ Services │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ │ ┌───────────────────────────────────────────────┐ │ │
│ │ │ UI 层 (responsive_builder + flutter_hms_sdk) │ │ │
│ │ └───────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 外部服务集成 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────┐ │ │
│ │ │ News API │ │ Push Kit │ │ Account │ │Anal. │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
1.2 项目目录结构
NewsHub/
├── lib/
│ ├── main.dart # 应用入口
│ ├── app.dart # 根组件
│ │
│ ├── core/
│ │ ├── config/
│ │ │ ├── app_config.dart # 应用配置
│ │ │ └── platform_config.dart # 平台配置
│ │ ├── constants/
│ │ │ └── api_constants.dart # API 常量
│ │ ├── theme/
│ │ │ ├── app_theme.dart # 主题配置
│ │ │ └── dark_theme.dart # 深色主题
│ │ └── utils/
│ │ ├── device_utils.dart # 设备工具
│ │ └── logger.dart # 日志工具
│ │
│ ├── features/
│ │ ├── home/
│ │ │ ├── presentation/
│ │ │ │ ├── pages/
│ │ │ │ │ └── home_page.dart
│ │ │ │ └── widgets/
│ │ │ │ ├── news_card.dart
│ │ │ │ └── category_chips.dart
│ │ │ └── domain/
│ │ │ ├── repositories/
│ │ │ └── usecases/
│ │ │
│ │ ├── news_detail/
│ │ │ ├── presentation/
│ │ │ │ ├── pages/
│ │ │ │ │ └── news_detail_page.dart
│ │ │ │ └── widgets/
│ │ │ │ └── content_reader.dart
│ │ │ └── domain/
│ │ │
│ │ ├── settings/
│ │ │ ├── presentation/
│ │ │ │ └── pages/
│ │ │ │ └── settings_page.dart
│ │ │ └── domain/
│ │ │
│ │ └── notifications/
│ │ ├── presentation/
│ │ │ └── pages/
│ │ │ └── notification_page.dart
│ │ └── domain/
│ │ └── hms_push_service.dart
│ │
│ └── shared/
│ ├── widgets/
│ │ ├── responsive_wrapper.dart # 响应式包装器
│ │ ├── adaptive_scaffold.dart # 自适应脚手架
│ │ └── custom_app_bar.dart # 自定义导航栏
│ └── domain/
│ ├── entities/
│ │ └── news_article.dart
│ └── repositories/
│ └── news_repository.dart
│
│ └── services/
│ ├── hms/
│ │ ├── hms_account_service.dart # HMS 账号服务
│ │ ├── hms_analytics_service.dart # HMS 分析服务
│ │ └── hms_push_service.dart # HMS 推送服务
│ └── api/
│ └── news_api_service.dart # 新闻 API 服务
│
├── android/
│ └── app/
│ └── src/
│ └── main/
│ └── AndroidManifest.xml # Android 配置
│
├── ios/
│ └── Runner/
│ └── Info.plist # iOS 配置
│
├── ohos/ # HarmonyOS 专用目录
│ ├── entry/
│ │ └── src/
│ │ └── main/
│ │ ├── ets/
│ │ │ └── entryability/
│ │ │ └── EntryAbility.ets
│ │ ├── resources/
│ │ │ └── base/
│ │ │ └── element/
│ │ │ └── string.json
│ │ └── module.json5 # HarmonyOS 模块配置
│ │
│ ├── harmony_flutter_adapter/ # Flutter-HarmonyOS 适配层
│ │ └── lib/
│ │ ├── harmony_plugins.dart # 插件适配
│ │ └── platform_channel.dart # 平台通道
│ │
│ └── build-profile.json5 # HarmonyOS 构建配置
│
├── pubspec.yaml # Flutter 依赖配置
├── build-profile.json5 # HarmonyOS 构建配置
└── hvigorfile.ts # HarmonyOS 构建脚本
二、DevEco Studio 环境配置
2.1 开发环境准备
| 工具/组件 | 版本要求 | 用途 |
|---|---|---|
| DevEco Studio | 4.0+ | HarmonyOS 应用开发 |
| Flutter SDK | 3.13.0+ | 跨平台框架 |
| Dart SDK | 3.0.0+ | Flutter 编程语言 |
| HarmonyOS SDK | API 9+ | 鸿蒙系统 API |
| JDK | 17 | Android 原生开发 |
2.2 创建 Flutter 项目
bash
# 1. 创建 Flutter 项目
flutter create --platforms=android,ios newshub
cd newshub
# 2. 添加鸿蒙支持
# 使用 flutter-harmonyos-adapter 或手动配置
2.3 pubspec.yaml 依赖配置
yaml
name: newshub
description: NewsHub - 跨平台新闻聚合应用
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# UI 框架
cupertino_icons: ^1.0.6
# 状态管理
flutter_bloc: ^8.1.3
equatable: ^2.0.5
# 网络请求
dio: ^5.3.2
pretty_dio_logger: ^1.3.1
# 本地存储
shared_preferences: ^2.2.2
hive: ^2.2.3
hive_flutter: ^1.1.0
# 响应式 UI
responsive_builder: ^0.7.0
# 华为 HMS 服务
flutter_hms_sdk:
git:
url: https://github.com/HMS-Core/hms-flutter-plugin.git
ref: harmonyos
path: flutter/
# 设备信息
device_info_plus: ^9.1.0
platform_info: ^4.0.2
# 其他工具
intl: ^0.18.0
url_launcher: ^6.1.11
share_plus: ^7.2.1
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
build_runner: ^2.4.6
freezed_annotation: ^2.4.1
flutter:
uses-material-design: true
assets:
- assets/images/
- assets/icons/
2.4 HarmonyOS 权限配置 (module.json5)
json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"default",
"tablet"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:icon",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:icon",
"startWindowBackgroundColor": "$color:start_window_background",
"exported": true,
"skills": [
{
"entities": [
"entity.system.home"
]
}
]
}
],
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:internet_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
},
{
"name": "ohos.permission.GET_NETWORK_INFO",
"reason": "$string:network_info_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.NOTIFICATION_CONTROLLER",
"reason": "$string:notification_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.KEEP_BACKGROUND_RUNNING",
"reason": "$string:background_permission_reason",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "always"
}
}
]
}
}
三、HMS 服务集成
3.1 Push Kit 消息推送服务
dart
// lib/services/hms/hms_push_service.dart
import 'package:flutter_hms_sdk/flutter_hms_sdk.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// HMS 推送消息数据模型
class HmsPushMessage {
final String title;
final String content;
final Map<String, dynamic>? data;
final String? messageId;
HmsPushMessage({
required this.title,
required this.content,
this.data,
this.messageId,
});
factory HmsPushMessage.fromJson(Map<String, dynamic> json) {
return HmsPushMessage(
title: json['title'] ?? '',
content: json['content'] ?? '',
data: json['data'],
messageId: json['messageId'],
);
}
Map<String, dynamic> toJson() {
return {
'title': title,
'content': content,
if (data != null) 'data': data,
if (messageId != null) 'messageId': messageId,
};
}
}
/// HMS Push Kit 推送服务
class HmsPushService {
static HmsPushService? _instance;
final FlutterHmsSdk _hmsSdk = FlutterHmsSdk();
final SharedPreferences _prefs = SharedPreferences.getInstance();
// 推送回调控制器
final _pushController = StreamController<HmsPushMessage>.broadcast();
Stream<HmsPushMessage> get pushStream => _pushController.stream;
factory HmsPushService() {
_instance ??= HmsPushService._internal();
return _instance!;
}
HmsPushService._internal();
/// 初始化 Push Kit
Future<bool> init() async {
try {
// 初始化 HMS SDK
await _hmsSdk.init(
appId: PlatformConfig.harmonyosAppId,
);
// 请求推送权限
final hasPermission = await _requestNotificationPermission();
if (!hasPermission) {
return false;
}
// 注册推送回调
_setupPushCallbacks();
// 获取 Token
final token = await _getPushToken();
if (token != null) {
await _saveToken(token);
}
return true;
} catch (e) {
AppLogger.error('HMS Push', '初始化失败: $e');
return false;
}
}
/// 请求通知权限
Future<bool> _requestNotificationPermission() async {
try {
final result = await _hmsSdk.requestNotificationPermission();
return result ?? false;
} catch (e) {
AppLogger.error('HMS Push', '权限请求失败: $e');
return false;
}
}
/// 获取推送 Token
Future<String?> _getPushToken() async {
try {
final token = await _hmsSdk.getToken();
AppLogger.info('HMS Push', 'Token 获取成功: ${token.substring(0, 10)}...');
return token;
} catch (e) {
AppLogger.error('HMS Push', 'Token 获取失败: $e');
return null;
}
}
/// 保存 Token 到本地
Future<void> _saveToken(String token) async {
try {
final prefs = await _prefs;
await prefs.setString('push_token', token);
await prefs.setString('push_token_update_time',
DateTime.now().toIso8601String());
} catch (e) {
AppLogger.error('HMS Push', 'Token 保存失败: $e');
}
}
/// 设置推送回调
void _setupPushCallbacks() {
// 接收消息回调
_hmsSdk.onMessageReceived((message) {
final pushMessage = HmsPushMessage.fromJson(message);
_pushController.add(pushMessage);
// 处理不同类型的推送
_handlePushMessage(pushMessage);
});
// Token 刷新回调
_hmsSdk.onTokenRefresh((token) {
AppLogger.info('HMS Push', 'Token 刷新: ${token.substring(0, 10)}...');
_saveToken(token);
});
// 错误回调
_hmsSdk.onPushError((error) {
AppLogger.error('HMS Push', '推送错误: $error');
});
}
/// 处理推送消息
void _handlePushMessage(HmsPushMessage message) {
switch (message.data?['type']) {
case 'news_breaking':
_handleBreakingNews(message);
break;
case 'daily_digest':
_handleDailyDigest(message);
break;
default:
_handleDefaultPush(message);
}
}
/// 处理突发新闻推送
void _handleBreakingNews(HmsPushMessage message) {
AppLogger.info('HMS Push', '突发新闻: ${message.title}');
// 显示本地通知
_showLocalNotification(
title: message.title,
body: message.content,
priority: NotificationPriority.high,
);
// 更新应用内角标
_updateBadgeCount();
}
/// 处理每日摘要推送
void _handleDailyDigest(HmsPushMessage message) {
AppLogger.info('HMS Push', '每日摘要');
// 缓存摘要数据
_cacheDailyDigest(message.data);
}
/// 处理默认推送
void _handleDefaultPush(HmsPushMessage message) {
AppLogger.info('HMS Push', '普通推送: ${message.title}');
}
/// 显示本地通知
Future<void> _showLocalNotification({
required String title,
required String body,
NotificationPriority priority = NotificationPriority.normal,
}) async {
try {
await _hmsSdk.showLocalNotification(
title: title,
body: body,
priority: priority,
);
} catch (e) {
AppLogger.error('HMS Push', '本地通知显示失败: $e');
}
}
/// 更新角标数量
Future<void> _updateBadgeCount() async {
try {
final prefs = await _prefs;
final currentCount = prefs.getInt('badge_count', 0);
await prefs.setInt('badge_count', currentCount + 1);
// 同步到鸿蒙桌面
await _hmsSdk.setBadgeCount(currentCount + 1);
} catch (e) {
AppLogger.error('HMS Push', '角标更新失败: $e');
}
}
/// 清除角标
Future<void> clearBadgeCount() async {
try {
final prefs = await _prefs;
await prefs.setInt('badge_count', 0);
await _hmsSdk.setBadgeCount(0);
} catch (e) {
AppLogger.error('HMS Push', '角标清除失败: $e');
}
}
/// 取消订阅
Future<void> unsubscribe() async {
try {
await _hmsSdk.deleteToken();
final prefs = await _prefs;
await prefs.remove('push_token');
} catch (e) {
AppLogger.error('HMS Push', '取消订阅失败: $e');
}
}
/// 释放资源
void dispose() {
_pushController.close();
}
}
3.2 Account Kit 账号服务
dart
// lib/services/hms/hms_account_service.dart
import 'package:flutter_hms_sdk/flutter_hms_sdk.dart';
/// 用户信息模型
class HmsUserInfo {
final String displayName;
final String? email;
final String? avatarUrl;
final String accountId;
HmsUserInfo({
required this.displayName,
this.email,
this.avatarUrl,
required this.accountId,
});
factory HmsUserInfo.fromJson(Map<String, dynamic> json) {
return HmsUserInfo(
displayName: json['displayName'] ?? '',
email: json['email'],
avatarUrl: json['avatarUrl'],
accountId: json['accountId'] ?? '',
);
}
}
/// HMS Account Kit 服务
class HmsAccountService {
static HmsAccountService? _instance;
final FlutterHmsSdk _hmsSdk = FlutterHmsSdk();
factory HmsAccountService() {
_instance ??= HmsAccountService._internal();
return _instance!;
}
HmsAccountService._internal();
/// 初始化 Account Kit
Future<bool> init() async {
try {
await _hmsSdk.init(
appId: PlatformConfig.harmonyosAppId,
);
AppLogger.info('HMS Account', '初始化成功');
return true;
} catch (e) {
AppLogger.error('HMS Account', '初始化失败: $e');
return false;
}
}
/// 静默登录
Future<HmsUserInfo?> silentSignIn() async {
try {
final result = await _hmsSdk.silentSignIn();
if (result == null) {
return null;
}
final userInfo = HmsUserInfo.fromJson(result);
await _saveUserInfo(userInfo);
return userInfo;
} catch (e) {
AppLogger.error('HMS Account', '静默登录失败: $e');
return null;
}
}
/// 显式登录
Future<HmsUserInfo?> signIn() async {
try {
final result = await _hmsSdk.signIn();
if (result == null) {
return null;
}
final userInfo = HmsUserInfo.fromJson(result);
await _saveUserInfo(userInfo);
// 登录成功后初始化分析服务
await HmsAnalyticsService().setUserId(userInfo.accountId);
return userInfo;
} catch (e) {
if (e.toString().contains('Canceled')) {
// 用户取消登录
return null;
}
AppLogger.error('HMS Account', '登录失败: $e');
return null;
}
}
/// 退出登录
Future<bool> signOut() async {
try {
await _hmsSdk.signOut();
// 清除本地用户信息
await _clearUserInfo();
// 重置分析用户 ID
await HmsAnalyticsService().resetUserId();
AppLogger.info('HMS Account', '退出登录成功');
return true;
} catch (e) {
AppLogger.error('HMS Account', '退出登录失败: $e');
return false;
}
}
/// 取消授权
Future<bool> revokeAuthorization() async {
try {
await _hmsSdk.revokeAuthorization();
return true;
} catch (e) {
AppLogger.error('HMS Account', '取消授权失败: $e');
return false;
}
}
/// 保存用户信息
Future<void> _saveUserInfo(HmsUserInfo userInfo) async {
try {
final prefs = await SharedPreferences.getInstance();
final json = jsonEncode(userInfo.toJson());
await prefs.setString('user_info', json);
} catch (e) {
AppLogger.error('HMS Account', '保存用户信息失败: $e');
}
}
/// 清除用户信息
Future<void> _clearUserInfo() async {
try {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('user_info');
} catch (e) {
AppLogger.error('HMS Account', '清除用户信息失败: $e');
}
}
/// 获取本地用户信息
Future<HmsUserInfo?> getLocalUserInfo() async {
try {
final prefs = await SharedPreferences.getInstance();
final json = prefs.getString('user_info');
if (json != null && json.isNotEmpty) {
return HmsUserInfo.fromJson(jsonDecode(json));
}
return null;
} catch (e) {
AppLogger.error('HMS Account', '获取本地用户信息失败: $e');
return null;
}
}
}
3.3 Analytics Kit 分析服务
dart
// lib/services/hms/hms_analytics_service.dart
import 'package:flutter_hms_sdk/flutter_hms_sdk.dart';
/// 分析事件类型
enum AnalyticsEventType {
pageView('page_view'),
newsClick('news_click'),
newsShare('news_share'),
newsFavorite('news_favorite'),
search('search'),
customEvent('custom_event');
final String value;
const AnalyticsEventType(this.value);
}
/// HMS Analytics Kit 服务
class HmsAnalyticsService {
static HmsAnalyticsService? _instance;
final FlutterHmsSdk _hmsSdk = FlutterHmsSdk();
String? _userId;
factory HmsAnalyticsService() {
_instance ??= HmsAnalyticsService._internal();
return _instance!;
}
HmsAnalyticsService._internal();
/// 初始化 Analytics Kit
Future<bool> init() async {
try {
await _hmsSdk.initAnalytics(
appId: PlatformConfig.harmonyosAppId,
);
AppLogger.info('HMS Analytics', '初始化成功');
return true;
} catch (e) {
AppLogger.error('HMS Analytics', '初始化失败: $e');
return false;
}
}
/// 设置用户 ID
Future<void> setUserId(String userId) async {
try {
await _hmsSdk.setUserId(userId);
_userId = userId;
AppLogger.info('HMS Analytics', '设置用户 ID: $userId');
} catch (e) {
AppLogger.error('HMS Analytics', '设置用户 ID 失败: $e');
}
}
/// 重置用户 ID
Future<void> resetUserId() async {
try {
await _hmsSdk.setUserId('');
_userId = null;
AppLogger.info('HMS Analytics', '重置用户 ID');
} catch (e) {
AppLogger.error('HMS Analytics', '重置用户 ID 失败: $e');
}
}
/// 发送页面浏览事件
Future<void> logPageView({
required String pageName,
String? pageClass,
}) async {
try {
await _hmsSdk.logEvent(
eventType: AnalyticsEventType.pageView.value,
eventName: pageName,
params: {
if (pageClass != null) 'page_class': pageClass,
'timestamp': DateTime.now().millisecondsSinceEpoch,
if (_userId != null) 'user_id': _userId,
},
);
} catch (e) {
AppLogger.error('HMS Analytics', '页面浏览事件发送失败: $e');
}
}
/// 发送新闻点击事件
Future<void> logNewsClick({
required String newsId,
required String category,
String? source,
}) async {
try {
await _hmsSdk.logEvent(
eventType: AnalyticsEventType.newsClick.value,
eventName: 'news_click',
params: {
'news_id': newsId,
'category': category,
if (source != null) 'source': source,
'timestamp': DateTime.now().millisecondsSinceEpoch,
},
);
} catch (e) {
AppLogger.error('HMS Analytics', '新闻点击事件发送失败: $e');
}
}
/// 发送新闻分享事件
Future<void> logNewsShare({
required String newsId,
required String platform,
}) async {
try {
await _hmsSdk.logEvent(
eventType: AnalyticsEventType.newsShare.value,
eventName: 'news_share',
params: {
'news_id': newsId,
'platform': platform,
'timestamp': DateTime.now().millisecondsSinceEpoch,
},
);
} catch (e) {
AppLogger.error('HMS Analytics', '新闻分享事件发送失败: $e');
}
}
/// 发送自定义事件
Future<void> logCustomEvent({
required String eventName,
Map<String, dynamic>? params,
}) async {
try {
await _hmsSdk.logEvent(
eventType: AnalyticsEventType.customEvent.value,
eventName: eventName,
params: {
...?params,
'timestamp': DateTime.now().millisecondsSinceEpoch,
},
);
} catch (e) {
AppLogger.error('HMS Analytics', '自定义事件发送失败: $e');
}
}
/// 设置用户属性
Future<void> setUserProfile({
String? favoriteCategory,
int? readingTime,
}) async {
try {
final params = <String, dynamic>{};
if (favoriteCategory != null) {
params['favorite_category'] = favoriteCategory;
}
if (readingTime != null) {
params['reading_time'] = readingTime;
}
await _hmsSdk.setUserProfile(params);
AppLogger.info('HMS Analytics', '设置用户属性: $params');
} catch (e) {
AppLogger.error('HMS Analytics', '设置用户属性失败: $e');
}
}
/// 开始会话
Future<void> beginSession({
String? sessionId,
}) async {
try {
await _hmsSdk.beginSession(
sessionId: sessionId ?? _generateSessionId(),
);
} catch (e) {
AppLogger.error('HMS Analytics', '开始会话失败: $e');
}
}
/// 结束会话
Future<void> endSession() async {
try {
await _hmsSdk.endSession();
} catch (e) {
AppLogger.error('HMS Analytics', '结束会话失败: $e');
}
}
/// 生成会话 ID
String _generateSessionId() {
return '${DateTime.now().millisecondsSinceEpoch}_${_userId ?? 'guest'}';
}
}
四、响应式 UI 构建
4.1 响应式包装器实现
dart
// lib/shared/widgets/responsive_wrapper.dart
import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';
/// 设备类型枚举
enum DeviceType {
mobile,
tablet,
desktop,
foldable,
}
/// 屏幕信息
class ScreenInfo {
final DeviceType deviceType;
final double screenWidth;
final double screenHeight;
final bool isFoldable;
final bool isDarkMode;
ScreenInfo({
required this.deviceType,
required this.screenWidth,
required this.screenHeight,
required this.isFoldable,
required this.isDarkMode,
});
}
/// 响应式包装器组件
class ResponsiveWrapper extends StatelessWidget {
final Widget Function(BuildContext, DeviceType) builder;
final Widget? mobile;
final Widget? tablet;
final Widget? desktop;
final Widget? foldable;
const ResponsiveWrapper({
Key? key,
required this.builder,
this.mobile,
this.tablet,
this.desktop,
this.foldable,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) {
// 判断设备类型
final deviceType = _getDeviceType(sizingInformation);
// 获取屏幕信息
final screenInfo = _getScreenInfo(context, sizingInformation, deviceType);
return _buildForDevice(context, deviceType, screenInfo);
},
);
}
DeviceType _getDeviceType(SizingInformation sizingInformation) {
final isFoldable = sizingInformation.deviceScreenType == DeviceScreenType.foldable;
if (isFoldable) {
return DeviceType.foldable;
}
if (sizingInformation.deviceScreenType == DeviceScreenType.desktop) {
return DeviceType.desktop;
}
if (sizingInformation.deviceScreenType == DeviceScreenType.tablet) {
return DeviceType.tablet;
}
return DeviceType.mobile;
}
ScreenInfo _getScreenInfo(
BuildContext context,
SizingInformation sizingInformation,
DeviceType deviceType,
) {
final isFoldable = deviceType == DeviceType.foldable;
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return ScreenInfo(
deviceType: deviceType,
screenWidth: sizingInformation.localWidgetSize.width,
screenHeight: sizingInformation.localWidgetSize.height,
isFoldable: isFoldable,
isDarkMode: isDarkMode,
);
}
Widget _buildForDevice(
BuildContext context,
DeviceType deviceType,
ScreenInfo screenInfo,
) {
// 优先使用指定的设备组件
if (deviceType == DeviceType.mobile && mobile != null) {
return mobile!;
}
if (deviceType == DeviceType.tablet && tablet != null) {
return tablet!;
}
if (deviceType == DeviceType.desktop && desktop != null) {
return desktop!;
}
if (deviceType == DeviceType.foldable && foldable != null) {
return foldable!;
}
// 使用 builder 函数
return builder(context, deviceType);
}
}
/// 响应式值辅助类
class ResponsiveValue<T> {
final T mobile;
final T? tablet;
final T? desktop;
final T? foldable;
const ResponsiveValue({
required this.mobile,
this.tablet,
this.desktop,
this.foldable,
});
T getValue(DeviceType deviceType) {
if (deviceType == DeviceType.foldable && foldable != null) {
return foldable!;
}
if (deviceType == DeviceType.desktop && desktop != null) {
return desktop!;
}
if (deviceType == DeviceType.tablet && tablet != null) {
return tablet!;
}
return mobile;
}
}
4.2 自适应脚手架实现
dart
// lib/shared/widgets/adaptive_scaffold.dart
import 'package:flutter/material.dart';
/// 自适应脚手架配置
class AdaptiveScaffoldConfig {
final bool showBottomNavigation;
final bool showNavigationRail;
final int navigationRailWidth;
final double? maxContentWidth;
final bool enableDrawer;
const AdaptiveScaffoldConfig({
this.showBottomNavigation = true,
this.showNavigationRail = false,
this.navigationRailWidth = 56,
this.maxContentWidth = 1200,
this.enableDrawer = false,
});
/// 根据设备类型创建配置
factory AdaptiveScaffoldConfig.forDevice(DeviceType deviceType) {
switch (deviceType) {
case DeviceType.mobile:
return const AdaptiveScaffoldConfig(
showBottomNavigation: true,
showNavigationRail: false,
enableDrawer: false,
);
case DeviceType.tablet:
return const AdaptiveScaffoldConfig(
showBottomNavigation: false,
showNavigationRail: true,
navigationRailWidth: 72,
enableDrawer: true,
);
case DeviceType.desktop:
return const AdaptiveScaffoldConfig(
showBottomNavigation: false,
showNavigationRail: true,
navigationRailRailWidth: 80,
enableDrawer: false,
maxContentWidth: 1200,
);
case DeviceType.foldable:
return const AdaptiveScaffoldConfig(
showBottomNavigation: false,
showNavigationRail: true,
navigationRailWidth: 72,
enableDrawer: true,
maxContentWidth: 1400,
);
}
}
}
/// 自适应脚手架组件
class AdaptiveScaffold extends StatelessWidget {
final AdaptiveScaffoldConfig config;
final PreferredSizeWidget? appBar;
final Widget? body;
final List<Widget>? floatingActions;
final Widget? drawer;
final List<NavigationDestination>? navigationDestinations;
final int selectedIndex;
final ValueChanged<int>? onDestinationSelected;
const AdaptiveScaffold({
Key? key,
required this.config,
this.appBar,
required this.body,
this.floatingActions,
this.drawer,
this.navigationDestinations,
this.selectedIndex = 0,
this.onDestinationSelected,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) {
final deviceType = _getDeviceType(sizingInformation);
switch (deviceType) {
case DeviceType.mobile:
return _buildMobileScaffold(context);
case DeviceType.tablet:
case DeviceType.foldable:
return _buildTabletScaffold(context);
case DeviceType.desktop:
return _buildDesktopScaffold(context);
}
},
);
}
Widget _buildMobileScaffold(BuildContext context) {
return Scaffold(
appBar: appBar,
body: body,
floatingActionButton: floatingActions?.isNotEmpty == true
? floatingActions!.first
: null,
bottomNavigationBar: navigationDestinations != null
? NavigationBar(
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: navigationDestinations!,
)
: null,
);
}
Widget _buildTabletScaffold(BuildContext context) {
return Scaffold(
appBar: appBar,
body: Row(
children: [
// 侧边导航栏
if (config.showNavigationRail && navigationDestinations != null)
NavigationRail(
width: config.navigationRailWidth.toDouble(),
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: navigationDestinations!,
),
// 抽屉菜单
if (config.enableDrawer && drawer != null)
Drawer(
width: 300,
child: drawer,
),
// 主内容区
Expanded(
child: _buildConstrainedBody(),
),
],
),
floatingActionButton: floatingActions?.isNotEmpty == true
? floatingActions!.first
: null,
);
}
Widget _buildDesktopScaffold(BuildContext context) {
return Scaffold(
appBar: appBar,
body: Row(
children: [
// 宽导航栏
if (config.showNavigationRail && navigationDestinations != null)
NavigationRail(
width: config.navigationRailWidth.toDouble(),
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
destinations: navigationDestinations!,
),
// 主内容区(限制最大宽度)
Expanded(
child: Center(
child: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: config.maxContentWidth ?? double.infinity,
),
child: _buildConstrainedBody(),
),
),
),
],
),
floatingActionButton: floatingActions?.isNotEmpty == true
? floatingActions!.first
: null,
);
}
Widget _buildConstrainedBody() {
return body ?? const SizedBox();
}
DeviceType _getDeviceType(SizingInformation sizingInformation) {
switch (sizingInformation.deviceScreenType) {
case DeviceScreenType.desktop:
return DeviceType.desktop;
case DeviceScreenType.tablet:
return DeviceType.tablet;
case DeviceScreenType.foldable:
return DeviceType.foldable;
default:
return DeviceType.mobile;
}
}
}
4.3 新闻卡片响应式实现
dart
// lib/features/home/presentation/widgets/news_card.dart
import 'package:flutter/material.dart';
import 'package:responsive_builder/responsive_builder.dart';
/// 新闻文章模型
class NewsArticle {
final String id;
final String title;
final String summary;
final String imageUrl;
final String category;
final DateTime publishTime;
final String source;
NewsArticle({
required this.id,
required this.title,
required this.summary,
required this.imageUrl,
required this.category,
required this.publishTime,
required this.source,
});
}
/// 新闻卡片组件
class NewsCard extends StatelessWidget {
final NewsArticle article;
final VoidCallback? onTap;
final VoidCallback? onFavorite;
const NewsCard({
Key? key,
required this.article,
this.onTap,
this.onFavorite,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInformation) {
// 根据屏幕大小调整卡片样式
final isMobile = sizingInformation.deviceScreenType == DeviceScreenType.mobile;
final isTablet = sizingInformation.deviceScreenType == DeviceScreenType.tablet;
return Card(
margin: EdgeInsets.symmetric(
horizontal: isMobile ? 12 : 16,
vertical: isMobile ? 6 : 8,
),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: onTap,
child: _buildCardContent(context, isMobile, isTablet),
),
);
},
);
}
Widget _buildCardContent(
BuildContext context,
bool isMobile,
bool isTablet,
) {
// 手机端:垂直布局
if (isMobile) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildImage(),
_buildTitleAndSummary(isVertical: true),
_buildFooter(),
],
);
}
// 平板端:水平布局
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildImage(width: 200),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitleAndSummary(isVertical: false),
const Spacer(),
_buildFooter(),
],
),
),
],
);
}
Widget _buildImage({double? width}) {
return CachedNetworkImage(
imageUrl: article.imageUrl,
width: width ?? double.infinity,
height: 200,
fit: BoxFit.cover,
placeholder: (context, url) => Container(
width: width ?? double.infinity,
height: 200,
color: Colors.grey[200],
child: const Center(
child: CircularProgressIndicator(),
),
),
errorWidget: (context, url, error) => Container(
width: width ?? double.infinity,
height: 200,
color: Colors.grey[300],
child: const Icon(Icons.broken_image),
),
);
}
Widget _buildTitleAndSummary({required bool isVertical}) {
return Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildCategoryChip(),
const SizedBox(height: 8),
Text(
article.title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
maxLines: isVertical ? 2 : 3,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),
Text(
article.summary,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: isVertical ? 2 : 4,
overflow: TextOverflow.ellipsis,
color: Colors.grey[600],
),
],
),
);
}
Widget _buildCategoryChip() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: _getCategoryColor(article.category),
borderRadius: BorderRadius.circular(12),
),
child: Text(
article.category,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.white,
),
),
);
}
Widget _buildFooter() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
Icon(Icons.source, size: 14, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
article.source,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
const Spacer(),
Icon(Icons.access_time, size: 14, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
_formatPublishTime(article.publishTime),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.grey[600],
),
),
],
),
);
}
Color _getCategoryColor(String category) {
final categoryColors = {
'科技': Colors.blue,
'财经': Colors.green,
'体育': Colors.orange,
'娱乐': Colors.purple,
'生活': Colors.teal,
};
return categoryColors[category] ?? Colors.grey;
}
String _formatPublishTime(DateTime time) {
final now = DateTime.now();
final difference = now.difference(time);
if (difference.inMinutes < 60) {
return '${difference.inMinutes}分钟前';
} else if (difference.inHours < 24) {
return '${difference.inHours}小时前';
} else {
return '${time.month}月${time.day}日';
}
}
}
五、鸿蒙平台专项优化
5.1 启动速度优化
dart
// lib/core/config/startup_optimizer.dart
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// 启动优化配置
class StartupOptimizer {
static const String _keyFirstLaunch = 'first_launch';
static const String _keyLastStartup = 'last_startup';
static const String _keyCachedData = 'cached_startup_data';
/// 初始化优化器
static Future<void> init() async {
final prefs = await SharedPreferences.getInstance();
// 检查是否首次启动
final isFirstLaunch = !prefs.containsKey(_keyFirstLaunch);
if (isFirstLaunch) {
await _optimizeFirstLaunch(prefs);
} else {
await _optimizeSubsequentLaunch(prefs);
}
}
/// 首次启动优化
static Future<void> _optimizeFirstLaunch(SharedPreferences prefs) async {
AppLogger.info('StartupOptimizer', '首次启动优化');
// 标记首次启动完成
await prefs.setBool(_keyFirstLaunch, true);
// 预加载关键数据
await _preloadCriticalData();
// 预编译 Dart 代码(AOT)
await _warmupCriticalPaths();
}
/// 后续启动优化
static Future<void> _optimizeSubsequentLaunch(SharedPreferences prefs) async {
final lastStartup = prefs.getString(_keyLastStartup);
if (lastStartup != null) {
final lastTime = DateTime.parse(lastStartup);
final daysSinceLastStartup = DateTime.now().difference(lastTime).inDays;
// 超过 7 天未使用,清理缓存
if (daysSinceLastStartup > 7) {
await _clearStaleCache();
}
}
await prefs.setString(_keyLastStartup, DateTime.now().toIso8601String());
// 异步恢复数据
_asyncRestoreData();
}
/// 预加载关键数据
static Future<void> _preloadCriticalData() async {
AppLogger.info('StartupOptimizer', '预加载关键数据');
// 并行加载关键数据
await Future.wait([
_loadConfigurations(),
_loadUserPreferences(),
_prepareOfflineData(),
]);
}
/// 加载配置
static Future<void> _loadConfigurations() async {
// 加载应用配置
await AppConfig.load();
}
/// 加载用户偏好
static Future<void> _loadUserPreferences() async {
// 加载用户偏好设置
final prefs = await SharedPreferences.getInstance();
final themeMode = prefs.getString('theme_mode') ?? 'system';
final language = prefs.getString('language') ?? 'system';
final fontSize = prefs.getDouble('font_size') ?? 1.0;
// 应用到全局配置
AppConfig.updateTheme(themeMode);
AppConfig.updateLanguage(language);
AppConfig.updateFontSize(fontSize);
}
/// 准备离线数据
static Future<void> _prepareOfflineData() async {
// 预缓存首页新闻数据
final newsRepository = NewsRepository();
await newsRepository.cacheTopHeadlines();
// 预加载分类数据
final categories = await newsRepository.getCategories();
await prefs.setString('cached_categories', jsonEncode(categories));
}
/// 预热关键路径
static Future<void> _warmupCriticalPaths() async {
AppLogger.info('StartupOptimizer', '预热关键路径');
// 预加载常用路由
_preloadRoutes();
}
/// 预加载路由
static void _preloadRoutes() {
// 预加载首页路由
runApp(
MaterialApp(
home: const SplashPage(),
routes: {
'/home': (context) => const HomePage(),
'/detail': (context) => const NewsDetailPage(),
'/settings': (context) => const SettingsPage(),
},
),
);
}
/// 异步恢复数据
static void _asyncRestoreData() {
AppLogger.info('StartupOptimizer', '异步恢复数据');
// 不阻塞启动,后台恢复
Future.microtask(() async {
await Future.wait([
_restoreUserSession(),
_restorePushToken(),
_syncOfflineChanges(),
]);
});
}
/// 恢复用户会话
static Future<void> _restoreUserSession() async {
final accountService = HmsAccountService();
final userInfo = await accountService.silentSignIn();
if (userInfo != null) {
AppLogger.info('StartupOptimizer', '用户会话已恢复');
}
}
/// 恢复推送 Token
static Future<void> _restorePushToken() async {
final prefs = await SharedPreferences.getInstance();
final token = prefs.getString('push_token');
if (token != null && token.isNotEmpty) {
final pushService = HmsPushService();
await pushService.init();
AppLogger.info('StartupOptimizer', '推送 Token 已恢复');
}
}
/// 同步离线变更
static Future<void> _syncOfflineChanges() async {
// 同步离线期间的用户操作
final syncService = SyncService();
await syncService.syncPendingChanges();
}
/// 清除过期缓存
static Future<void> _clearStaleCache() async {
AppLogger.info('StartupOptimizer', '清除过期缓存');
final prefs = await SharedPreferences.getInstance();
// 清除旧的缓存数据
await prefs.remove('cached_home_data');
await prefs.remove('cached_categories');
// 清除数据库旧数据
final database = await AppDatabase.getInstance();
await database.clearOldRecords(days: 30);
}
}
5.2 后台保活策略
dart
// lib/services/background/background_keep_alive_service.dart
import 'dart:io';
import 'package:flutter/services.dart';
/// 后台任务类型
enum BackgroundTaskType {
dataSync,
contentRefresh,
pushHeartbeat,
analyticsUpload,
}
/// 后台保活服务
class BackgroundKeepAliveService {
static BackgroundKeepAliveService? _instance;
static const MethodChannel _channel =
MethodChannel('com.example.newshub/background');
factory BackgroundKeepAliveService() {
_instance ??= BackgroundKeepAliveService._internal();
return _instance!;
}
BackgroundKeepAliveService._internal() {
_setupMethodCallHandler();
}
/// 初始化后台保活
Future<void> init() async {
try {
// 检查是否为鸿蒙平台
if (Platform.isAndroid) {
final isHarmonyOS = await _checkHarmonyOS();
if (isHarmonyOS) {
await _initHarmonyBackgroundTasks();
} else {
await _initAndroidBackgroundTasks();
}
}
AppLogger.info('BackgroundService', '后台保活初始化成功');
} catch (e) {
AppLogger.error('BackgroundService', '初始化失败: $e');
}
}
/// 检测鸿蒙平台
Future<bool> _checkHarmonyOS() async {
try {
final result = await _channel.invokeMethod('checkPlatform');
return result == 'harmonyos';
} catch (e) {
return false;
}
}
/// 初始化鸿蒙后台任务
Future<void> _initHarmonyBackgroundTasks() async {
// 启动心跳任务
await _startHeartbeatTask();
// 启动数据同步任务
await _startDataSyncTask();
// 请求后台运行权限
await _requestBackgroundPermission();
}
/// 启动心跳任务
Future<void> _startHeartbeatTask() async {
try {
await _channel.invokeMethod('startHeartbeatTask', {
'interval': 15 * 60 * 1000, // 15 分钟
});
AppLogger.info('BackgroundService', '心跳任务已启动');
} catch (e) {
AppLogger.error('BackgroundService', '启动心跳任务失败: $e');
}
}
/// 启动数据同步任务
Future<void> _startDataSyncTask() async {
try {
await _channel.invokeMethod('startDataSyncTask', {
'interval': 60 * 60 * 1000, // 1 小时
'networkRequired': true,
});
AppLogger.info('BackgroundService', '数据同步任务已启动');
} catch (e) {
AppLogger.error('BackgroundService', '启动数据同步任务失败: $e');
}
}
/// 请求后台运行权限
Future<void> _requestBackgroundPermission() async {
try {
await _channel.invokeMethod('requestBackgroundPermission');
} catch (e) {
AppLogger.error('BackgroundService', '请求后台权限失败: $e');
}
}
/// 初始化 Android 后台任务
Future<void> _initAndroidBackgroundTasks() async {
// Android 使用 WorkManager 或 Foreground Service
await _channel.invokeMethod('initAndroidBackground');
}
/// 设置方法调用处理器
void _setupMethodCallHandler() {
_channel.setMethodCallHandler((call) async {
switch (call.method) {
case 'onBackgroundTask':
final taskType = call.arguments['taskType'];
await _handleBackgroundTask(taskType);
break;
case 'onTaskRemoved':
await _handleTaskRemoved();
break;
default:
throw UnimplementedError('未实现的方法: ${call.method}');
}
});
}
/// 处理后台任务
Future<void> _handleBackgroundTask(String taskTypeStr) async {
final taskType = BackgroundTaskType.values.firstWhere(
(e) => e.toString() == taskTypeStr,
orElse: () => BackgroundTaskType.dataSync,
);
switch (taskType) {
case BackgroundTaskType.dataSync:
await _syncData();
break;
case BackgroundTaskType.contentRefresh:
await _refreshContent();
break;
case BackgroundTaskType.pushHeartbeat:
await _sendHeartbeat();
break;
case BackgroundTaskType.analyticsUpload:
await _uploadAnalytics();
break;
}
}
/// 同步数据
Future<void> _syncData() async {
AppLogger.info('BackgroundService', '执行数据同步');
try {
final syncService = SyncService();
await syncService.syncNews();
await syncService.syncUserPreferences();
} catch (e) {
AppLogger.error('BackgroundService', '数据同步失败: $e');
}
}
/// 刷新内容
Future<void> _refreshContent() async {
AppLogger.info('BackgroundService', '刷新内容');
try {
final newsRepository = NewsRepository();
await newsRepository.refreshTopHeadlines();
// 发送更新通知
final pushService = HmsPushService();
await pushService.showLocalNotification(
title: '内容已更新',
body: '查看最新新闻',
priority: NotificationPriority.low,
);
} catch (e) {
AppLogger.error('BackgroundService', '内容刷新失败: $e');
}
}
/// 发送心跳
Future<void> _sendHeartbeat() async {
AppLogger.info('BackgroundService', '发送心跳');
try {
final analyticsService = HmsAnalyticsService();
await analyticsService.logCustomEvent(
eventName: 'heartbeat',
params: {'timestamp': DateTime.now().millisecondsSinceEpoch},
);
} catch (e) {
AppLogger.error('BackgroundService', '心跳失败: $e');
}
}
/// 上传分析数据
Future<void> _uploadAnalytics() async {
AppLogger.info('BackgroundService', '上传分析数据');
try {
final analyticsService = HmsAnalyticsService();
await analyticsService.endSession();
await analyticsService.beginSession();
} catch (e) {
AppLogger.error('BackgroundService', '分析上传失败: $e');
}
}
/// 任务被移除
Future<void> _handleTaskRemoved() async {
AppLogger.warning('BackgroundService', '后台任务被移除');
// 尝试重新调度
await Future.delayed(const Duration(seconds: 5));
await init();
}
}
5.3 深色主题适配
dart
// lib/core/theme/dark_theme.dart
import 'package:flutter/material.dart';
/// 主题配置类
class ThemeConfig {
static const String _keyThemeMode = 'theme_mode';
/// 获取主题模式
static Future<ThemeMode> getThemeMode() async {
final prefs = await SharedPreferences.getInstance();
final savedMode = prefs.getString(_keyThemeMode);
switch (savedMode) {
case 'light':
return ThemeMode.light;
case 'dark':
return ThemeMode.dark;
default:
return ThemeMode.system;
}
}
/// 设置主题模式
static Future<void> setThemeMode(ThemeMode mode) async {
final prefs = await SharedPreferences.getInstance();
final modeString = mode == ThemeMode.light
? 'light'
: mode == ThemeMode.dark
? 'dark'
: 'system';
await prefs.setString(_keyThemeMode, modeString);
}
/// 构建亮色主题
static ThemeData buildLightTheme() {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
colorScheme: _lightColorScheme,
scaffoldBackgroundColor: _lightColorScheme.surface,
appBarTheme: AppBarTheme(
backgroundColor: _lightColorScheme.primary,
foregroundColor: _lightColorScheme.onPrimary,
elevation: 0,
centerTitle: true,
),
cardTheme: CardTheme(
color: _lightColorScheme.surface,
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
navigationBarTheme: NavigationBarTheme(
backgroundColor: _lightColorScheme.surface,
indicatorColor: _lightColorScheme.primary,
),
);
}
/// 构建深色主题
static ThemeData buildDarkTheme() {
return ThemeData(
useMaterial3: 3,
brightness: Brightness.dark,
colorScheme: _darkColorScheme,
scaffoldBackgroundColor: _darkColorScheme.surface,
appBarTheme: AppBarTheme(
backgroundColor: _darkColorScheme.surface,
foregroundColor: _darkColorScheme.onSurface,
elevation: 0,
centerTitle: true,
),
cardTheme: CardTheme(
color: _darkColorScheme.surfaceVariant,
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
navigationBarTheme: NavigationBarTheme(
backgroundColor: _darkColorScheme.surface,
indicatorColor: _darkColorScheme.primary,
),
);
}
/// 亮色配色方案
static const ColorScheme _lightColorScheme = ColorScheme.light(
primary: Color(0xFF1976D2),
onPrimary: Colors.white,
primaryContainer: Color(0xFFBBDEFB),
onPrimaryContainer: Color(0xFF0D47A1),
secondary: Color(0xFF26A69A),
onSecondary: Colors.white,
secondaryContainer: Color(0xFFB2DFDB),
onSecondaryContainer: Color(0xFF004D40),
tertiary: Color(0xFFFF9800),
onTertiary: Colors.white,
tertiaryContainer: Color(0xFFFFCC80),
onTertiaryContainer: Color(0xFF5E3A00),
error: Color(0xFFD32F2F),
onError: Colors.white,
background: Color(0xFFFAFAFA),
onBackground: Color(0xFF121212),
surface: Color(0xFFFFFFFF),
onSurface: Color(0xFF121212),
surfaceVariant: Color(0xFFF5F5F5),
onSurfaceVariant: Color(0xFF121212),
outline: Color(0xFFE0E0E0),
outlineVariant: Color(0xFFC2C2C2),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFF121212),
onInverseSurface: Color(0xFFE0E0E0),
inversePrimary: Color(0xFF90CAF9),
);
/// 深色配色方案
static const ColorScheme _darkColorScheme = ColorScheme.dark(
primary: Color(0xFF90CAF9),
onPrimary: Color(0xFF000000),
primaryContainer: Color(0xFF0D47A1),
onPrimaryContainer: Color(0xFFBBDEFB),
secondary: Color(0xFF80CBC4),
onSecondary: Color(0xFF000000),
secondaryContainer: Color(0xFF004D40),
onSecondaryContainer: Color(0xFFB2DFDB),
tertiary: Color(0xFFFFB74D),
onTertiary: Color(0xFF000000),
tertiaryContainer: Color(0xFF5E3A00),
onTertiaryContainer: Color(0xFFFFCC80),
error: Color(0xFFFF5252),
onError: Color(0xFF000000),
background: Color(0xFF121212),
onBackground: Color(0xFFE0E0E0),
surface: Color(0xFF1E1E1E),
onSurface: Color(0xFFE0E0E0),
surfaceVariant: Color(0xFF2C2C2C),
onSurfaceVariant: Color(0xFFE0E0E0),
outline: Color(0xFF3C3C3C),
outlineVariant: Color(0xFF4C4C4C),
shadow: Color(0xFF000000),
scrim: Color(0xFF000000),
inverseSurface: Color(0xFFE0E0E0),
onInverseSurface: Color(0xFF121212),
inversePrimary: Color(0xFF1976D2),
);
}
六、.hap 包打包与上架
6.1 HarmonyOS 构建配置
json5
// build-profile.json5
{
"apiType": "stageMode",
"buildOption": {
"externalNativeOptions": {
"path": "./src/main/cpp/CMakeLists.txt",
"arguments": "",
"abiFilters": [
"armeabi-v7a",
"arm64-v8a"
],
"cppFlags": ""
}
},
"targets": [
{
"name": "default",
"runtimeOS": "HarmonyOS"
}
]
}
json5
// ohos/entry/build-profile.json5
{
"apiType": "stageMode",
"buildOption": {},
"targets": [
{
"name": "default",
"runtimeOS": "HarmonyOS"
}
],
"hapPackages": [
{
"name": "entry",
"moduleName": "entry",
"outPath": "../build/harmonyos/outputs/hap"
}
]
}
6.2 Flutter 鸿蒙打包脚本
bash
#!/bin/bash
# build_harmonyos.sh
echo "开始构建 NewsHub HarmonyOS 版本..."
# 1. 清理旧构建
flutter clean
# 2. 获取依赖
flutter pub get
# 3. 构建 Flutter 鸿蒙产物
flutter build harmonyos --release
# 4. 进入鸿蒙项目目录
cd ohos
# 5. 使用 DevEco 构建工具
# 构建前先执行 hvigorw assembleHap
# 6. 签名配置
# 需要在本地配置签名证书
# 7. 最终产物路径
# build/harmonyos/outputs/hap/release/entry-default-signed.hap
echo "构建完成!产物位于: build/harmonyos/outputs/hap/"
6.3 AppGallery 上架清单
| 检查项 | 要求 | 说明 |
|---|---|---|
| 应用图标 | 512x512 PNG | 符合华为设计规范 |
| 应用截图 | 至少 3 张 | 手机、平板各尺寸 |
| 应用描述 | 中文/英文 | 80-4000 字符 |
| 隐私政策 | 必填项 | HTTPS 链接 |
| 内容分级 | 正确填写 | 年龄分级 |
| 权限说明 | 详尽说明 | 用户可理解 |
| 测试账号 | 如需登录 | 提供测试账号 |
| 关键词 | 准确描述 | 提升搜索排名 |
七、总结
7.1 技术要点总结
本教程覆盖了 Flutter 跨平台开发的完整流程,特别是在鸿蒙平台上的适配:
-
一套代码,三端运行
- Android、iOS、HarmonyOS 统一业务逻辑
- 平台特定代码隔离
-
HMS 服务完整集成
- Push Kit: 消息推送
- Account Kit: 账号登录
- Analytics Kit: 数据分析
-
响应式 UI 设计
- 手机/平板/折叠屏自适应
- 深色主题支持
-
鸿蒙平台专项优化
- 启动速度优化
- 后台保活策略
- 性能调优
-
完整打包上架流程
- .hap 包构建
- AppGallery 上架
7.2 技术栈总览
┌─────────────────────────────────────────────────────────────┐
│ NewsHub 技术栈全景图 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Flutter 跨平台框架 │ │
│ │ ┌─────────────────────────────────────┐ │ │
│ │ │ Dart 语言 + Flutter Engine │ │ │
│ │ │ - BLoC 状态管理 │ │ │
│ │ │ - responsive_builder │ │ │
│ │ └─────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ HMS Core 服务 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐│ │
│ │ │ Push Kit │ │Account Kit│ │Analytics ││ │
│ │ │ 消息推送 │ │账号登录 │ │数据分析 ││ │
│ │ └──────────┘ └──────────┘ └──────────┘│ │
│ │ ┌─────────────────────────────────────────┐ │ │
│ │ │ flutter_hms_sdk 社区插件 │ │ │
│ │ └─────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ HarmonyOS 原生层 │ │
│ │ - DevEco Studio 项目配置 │ │
│ │ - ArkTS 适配层 │ │
│ │ - .hap 包打包 │ │
│ └──────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
7.3 后续扩展方向
- 集成 HMS Location Kit 实现本地新闻推荐
- 使用 HMS Scan Kit 实现新闻二维码分享
- 集成 HMS ML Kit 实现新闻内容分类
- 添加折叠屏展开/折叠状态监听
- 实现 TV 大屏适配
- 添加手表端应用
关键词: Flutter、HarmonyOS、HMS Core、Push Kit、跨平台、responsive_builder、DevEco Studio、.hap包
项目完整代码: 请参考项目目录结构实现各模块
作者注: 本教程基于 Flutter 3.13+ 和 HarmonyOS API 9+,建议在真机上测试推送功能。