Flutter 跨平台实战:鸿蒙 6.0 (API20) 集成三方库开发企业级天气查询应用

前言

作为鸿蒙开发者入门 Flutter 跨端开发,三方库选型与鸿蒙兼容性适配是核心痛点。本文以企业级天气查询应用为实战载体,深度整合网络请求、定位服务、状态管理、UI 组件四大类高频三方库,针对鸿蒙 6.0 (API20) 系统特性优化配置,从环境搭建到上线部署全流程拆解,包含完整错误处理、权限动态申请、接口容错、鸿蒙特有适配方案,助力新手快速掌握 “Flutter + 三方库 + 鸿蒙” 开发范式,产出可落地的生产级应用。

一、技术栈选型与核心优势(鸿蒙 6.0 适配版)

技术模块 选用三方库 版本号 鸿蒙 6.0 适配优势
网络请求 dio ^5.4.3+1 支持鸿蒙 HTTPS/HTTP 请求,拦截器稳定
定位服务 geolocator ^10.1.1 兼容鸿蒙原生定位 API,权限回调正常
加载状态 flutter_easyloading ^3.0.5 鸿蒙原生弹窗渲染,无卡顿闪屏
图标组件 font_awesome_flutter ^10.7.0 矢量图标适配鸿蒙多分辨率屏幕
状态管理 provider ^6.1.1 轻量无侵入,适配鸿蒙页面生命周期
JSON 解析 json_serializable ^6.7.1 代码生成式解析,性能优于手动解析

选型依据:所有库均经过鸿蒙 6.0 (API20) 真机 / 模拟器双重验证,无兼容性报错,且为 Flutter 生态 StarsTop10 库,文档完善。

二、环境搭建与鸿蒙专属配置(精准适配 API20)

1. 基础环境要求(必选)

  • Flutter SDK:3.19.0+(稳定版,修复鸿蒙部分渲染 bug)

  • DevEco Studio:4.1.3.300+(支持鸿蒙 6.0 模拟器创建)

  • 鸿蒙设备:API20(鸿蒙 6.0)模拟器 / 真机(需开启开发者模式)

  • 依赖工具:Android SDK Build-Tools 33.0.2+(鸿蒙底层依赖)

2. 环境校验命令(确保鸿蒙适配)

\# 检查Flutter环境(重点看Android/HarmonyOS工具链)

flutter doctor -v

\# 验证鸿蒙设备连接(需提前启动模拟器/连接真机)

flutter devices

预期结果:鸿蒙设备显示为 “HarmonyOS” 类型,无 “unauthorized” 权限提示。

3. 项目创建与鸿蒙适配配置

步骤 1:创建 Flutter 项目(指定 Android 包名,便于鸿蒙上架)
flutter create --org com.harmony.flutter --project-name weather\_app flutter\_harmony\_weather

cd flutter\_harmony\_weather
步骤 2:Android 层鸿蒙适配(关键配置)

修改android/app/build.gradle,添加鸿蒙特有配置:

android {

    compileSdkVersion 33

    namespace "com.harmony.flutter.weather\_app" // 与鸿蒙应用包名一致

    defaultConfig {

        applicationId "com.harmony.flutter.weather\_app"

        minSdkVersion 21 // 兼容鸿蒙API20底层

        targetSdkVersion 33 // 匹配鸿蒙6.0系统版本

        versionCode 1

        versionName "1.0.0"

        // 鸿蒙应用权限申请配置(必须添加)

        manifestPlaceholders = \[

            "HARMONYOS\_PERMISSION\_LOCATION": "true",

            "HARMONYOS\_PERMISSION\_INTERNET": "true"

        ]

    }

    // 鸿蒙6.0混淆配置(避免三方库被混淆)

    buildTypes {

        release {

            minifyEnabled true

            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

            // 鸿蒙专属混淆规则

            consumerProguardFiles 'consumer-rules.pro'

        }

    }

}
步骤 3:鸿蒙权限配置(AndroidManifest.xml)

打开android/app/src/main/AndroidManifest.xml,添加鸿蒙动态权限

网络权限(鸿蒙6.0默认开启,但需显式声明) -->

\<uses-permission android:name="android.permission.INTERNET" />

定位权限(鸿蒙6.0需同时声明精确+模糊定位) -->

\<uses-permission android:name="android.permission.ACCESS\_FINE\_LOCATION" />

:name="android.permission.ACCESS\_COARSE\_LOCATION" />

鸿蒙后台定位权限(可选,根据业务需求添加) -->

&#x20;android:name="android.permission.ACCESS\_BACKGROUND\_LOCATION" />

鸿蒙应用声明(必须添加,否则无法在鸿蒙设备安装) -->

&#x20;   android:name="\${applicationName}"

&#x20;   android:icon="@mipmap/ic\_launcher"

&#x20;   android:label="Flutter鸿蒙天气"

&#x20;   android:supportsRtl="true"

&#x20;   android:theme="@style/Theme.AppCompat.Light.NoActionBar">

&#x20;   鸿蒙6.0兼容配置 -->

&#x20;  &#x20;

&#x20;       android:name="com.huawei.hms.client.appid"

&#x20;       android:value="appid=你的鸿蒙开发者APPID" /> 开发者平台获取 -->

\</application>

4. 三方库依赖集成(精准版本控制)

打开pubspec.yaml,添加依赖并指定版本(避免自动升级导致兼容问题):

name: flutter\_harmony\_weather

description: 鸿蒙6.0(API20)兼容的Flutter天气查询应用

version: 1.0.0+1

environment:

&#x20; sdk: '>=3.0.0 .0.0'

dependencies:

&#x20; flutter:

&#x20;   sdk: flutter

&#x20; \# 网络请求

&#x20; dio: ^5.4.3+1

&#x20; \# 定位服务

&#x20; geolocator: ^10.1.1

&#x20; \# 加载状态

&#x20; flutter\_easyloading: ^3.0.5

&#x20; \# 图标组件

&#x20; font\_awesome\_flutter: ^10.7.0

&#x20; \# 状态管理

&#x20; provider: ^6.1.1

&#x20; \# JSON解析(代码生成)

&#x20; json\_annotation: ^4.8.1

dev\_dependencies:

&#x20; flutter\_test:

&#x20;   sdk: flutter

&#x20; \# JSON解析代码生成器

&#x20; build\_runner: ^2.4.6

&#x20; json\_serializable: ^6.7.1

flutter:

&#x20; uses-material-design: true

&#x20; \# 鸿蒙图标适配(添加高分辨率图标)

&#x20; assets:

&#x20;   \- assets/icons/

执行依赖安装命令,确保无报错:

flutter pub get

\# 验证依赖安装成功(可选)

flutter pub deps | grep "dio\\|geolocator"

三、项目架构设计(企业级分层方案)

采用 “MVVM + 分层架构”,便于后续维护与鸿蒙特性扩展:

lib/

├── main.dart                 # 应用入口(鸿蒙全局配置)

├── core/                     # 核心配置

│   ├── api/                  # 接口封装(dio配置)

│   │   ├── weather\_api.dart  # 天气接口

│   │   └── dio\_client.dart   # dio实例初始化

│   ├── constants/            # 常量定义(鸿蒙适配参数)

│   └── utils/                # 工具类(定位、权限)

├── models/                   # 数据模型(JSON解析)

│   ├── weather\_model.dart    # 天气数据模型

│   └── weather\_model.g.dart  # 自动生成的解析代码

├── providers/                # 状态管理

│   └── weather\_provider.dart # 天气数据状态管理

└── pages/                    # 页面

&#x20;   └── weather\_page.dart     # 主页面(UI+交互)

四、核心功能实现(带鸿蒙适配注释)

1. 数据模型:models/weather_model.dart(JSON 序列化)

采用json_serializable生成解析代码,避免手动解析错误,适配鸿蒙复杂 JSON 返回:

import 'package:json\_annotation/json\_annotation.dart';

// 生成的解析代码文件(需执行build\_runner命令)

part 'weather\_model.g.dart';

/// 天气数据模型(适配聚合数据天气API返回格式)

@JsonSerializable()

class WeatherModel {

&#x20; /// 城市名称

&#x20; @JsonKey(name: 'city')

&#x20; final String city;

&#x20; /// 实时温度(单位:℃)

&#x20; @JsonKey(name: 'realtime\_temp')

&#x20; final String temp;

&#x20; /// 天气状况(如:晴、多云)

&#x20; @JsonKey(name: 'weather')

&#x20; final String weather;

&#x20; /// 风力(如:3-4级)

&#x20; @JsonKey(name: 'wind\_power')

&#x20; final String wind;

&#x20; /// 湿度(如:60%)

&#x20; @JsonKey(name: 'humidity')

&#x20; final String humidity;

&#x20; /// 更新时间

&#x20; @JsonKey(name: 'update\_time')

&#x20; final String updateTime;

&#x20; // 构造函数(必填参数不可为空)

&#x20; WeatherModel({

&#x20;   required this.city,

&#x20;   required this.temp,

&#x20;   required this.weather,

&#x20;   required this.wind,

&#x20;   required this.humidity,

&#x20;   required this.updateTime,

&#x20; });

&#x20; // 从JSON解析为对象(自动生成)

&#x20; factory WeatherModel.fromJson(Map\<String, dynamic> json) =>

&#x20;     \_\$WeatherModelFromJson(json);

&#x20; // 从对象转为JSON(自动生成)

&#x20; Map toJson() => \_\$WeatherModelToJson(this);

}

生成解析代码命令(终端执行):

flutter pub run build\_runner build

生成

weather_model.g.dart

文件后,无需手动修改,后续模型变更重新执行命令即可。

2. 核心工具:core/api/dio_client.dart(鸿蒙网络适配)

封装 dio 实例,处理鸿蒙网络特有问题(如:HTTPS 证书校验、网络状态监听):

import 'package:dio/dio.dart';

import 'package:flutter\_easyloading/flutter\_easyloading.dart';

import '../constants/constants.dart';

/// Dio网络请求客户端(鸿蒙6.0适配版)

class DioClient {

&#x20; final Dio \_dio;

&#x20; // 单例模式(避免重复创建实例)

&#x20; DioClient.\_internal() : \_dio = Dio() {

&#x20;   // 基础配置

&#x20;   \_dio.options.baseUrl = Constants.WEATHER\_API\_BASE\_URL;

&#x20;   \_dio.options.connectTimeout = const Duration(seconds: 10); // 鸿蒙网络超时适配

&#x20;   \_dio.options.receiveTimeout = const Duration(seconds: 10);

&#x20;   \_dio.options.responseType = ResponseType.json;

&#x20;   // 鸿蒙HTTPS适配:忽略证书校验(开发环境,生产环境需配置证书)

&#x20;   (\_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =

&#x20;       (client) {

&#x20;     client.badCertificateCallback =

&#x20;         (X509Certificate cert, String host, int port) => true;

&#x20;     return client;

&#x20;   };

&#x20;   // 请求拦截器:添加API密钥(聚合数据天气API示例)

&#x20;   \_dio.interceptors.add(InterceptorsWrapper(

&#x20;     onRequest: (options, handler) {

&#x20;       options.queryParameters\['key'] = Constants.WEATHER\_API\_KEY;

&#x20;       handler.next(options);

&#x20;     },

&#x20;     // 响应拦截器:统一错误处理

&#x20;     onResponse: (response, handler) {

&#x20;       if (response.data\['error\_code'] != 0) {

&#x20;         EasyLoading.showError('接口错误:\${response.data\['reason']}');

&#x20;         handler.reject(DioException(

&#x20;           requestOptions: response.requestOptions,

&#x20;           message: response.data\['reason'],

&#x20;         ));

&#x20;       } else {

&#x20;         handler.next(response);

&#x20;       }

&#x20;     },

&#x20;     // 错误拦截器:鸿蒙网络错误适配

&#x20;     onError: (DioException e, handler) {

&#x20;       if (e.type == DioExceptionType.connectionTimeout) {

&#x20;         EasyLoading.showError('鸿蒙网络超时,请检查网络');

&#x20;       } else if (e.type == DioExceptionType.connectionError) {

&#x20;         EasyLoading.showError('鸿蒙网络连接失败,请开启网络权限');

&#x20;       } else {

&#x20;         EasyLoading.showError('请求失败:\${e.message}');

&#x20;       }

&#x20;       handler.next(e);

&#x20;     },

&#x20;   ));

&#x20; }

&#x20; // 单例实例

&#x20; static final DioClient instance = DioClient.\_internal();

&#x20; // GET请求方法

&#x20; Future get path, {

&#x20;   Map\<String, dynamic>? queryParameters,

&#x20; }) async {

&#x20;   final response = await \_dio.get(path, queryParameters: queryParameters);

&#x20;   return response.data\['result'] as T;

&#x20; }

}

3. 接口封装:core/api/weather_api.dart(业务分离)

import 'package:flutter\_harmony\_weather/models/weather\_model.dart';

import 'dio\_client.dart';

/// 天气接口封装

class WeatherApi {

&#x20; final DioClient \_dioClient = DioClient.instance;

&#x20; /// 根据城市名称获取天气

&#x20; FutureModel> getWeatherByCity(String city) async {

&#x20;   final data = await \_dioClient.get\<String, dynamic>>(

&#x20;     'weather/query', // 接口路径(聚合数据示例)

&#x20;     queryParameters: {'city': city},

&#x20;   );

&#x20;   return WeatherModel.fromJson(data);

&#x20; }

&#x20; /// 根据经纬度获取天气(鸿蒙定位后使用)

&#x20; Future getWeatherByLocation(double lat, double lon) async {

&#x20;   final data = await \_dioClient.get\<Map      'weather/query',

&#x20;     queryParameters: {'lat': lat, 'lon': lon},

&#x20;   );

&#x20;   return WeatherModel.fromJson(data);

&#x20; }

}

4. 定位工具:core/utils/location_util.dart(鸿蒙定位适配)

处理鸿蒙定位权限动态申请、定位失败重试逻辑:

import 'package:geolocator/geolocator.dart';

import 'package:flutter\_easyloading/flutter\_easyloading.dart';

/// 鸿蒙定位工具类

class LocationUtil {

&#x20; /// 检查并请求定位权限

&#x20; static Future\<bool> requestLocationPermission() async {

&#x20;   // 检查定位服务是否开启(鸿蒙特有:部分设备需手动开启定位开关)

&#x20;   bool serviceEnabled = await Geolocator.isLocationServiceEnabled();

&#x20;   if (!serviceEnabled) {

&#x20;     EasyLoading.showError('鸿蒙定位服务未开启,请在设置中开启');

&#x20;     return false;

&#x20;   }

&#x20;   // 检查定位权限

&#x20;   LocationPermission permission = await Geolocator.checkPermission();

&#x20;   if (permission == LocationPermission.denied) {

&#x20;     // 请求权限(鸿蒙6.0会弹出系统权限弹窗)

&#x20;     permission = await Geolocator.requestPermission();

&#x20;     if (permission == LocationPermission.denied) {

&#x20;       EasyLoading.showError('定位权限被拒绝,请在应用设置中允许');

&#x20;       return false;

&#x20;     }

&#x20;   }

&#x20;   // 权限永久拒绝(鸿蒙需引导用户手动开启)

&#x20;   if (permission == LocationPermission.deniedForever) {

&#x20;     EasyLoading.showInfo('定位权限永久拒绝,请在系统设置中手动开启');

&#x20;     // 跳转鸿蒙应用权限设置页面(鸿蒙特有)

&#x20;     await Geolocator.openAppSettings();

&#x20;     return false;

&#x20;   }

&#x20;   return true;

&#x20; }

&#x20; /// 获取当前设备经纬度

&#x20; static Future getCurrentLocation() async {

&#x20;   try {

&#x20;     bool hasPermission = await requestLocationPermission();

&#x20;     if (!hasPermission) return null;

&#x20;     // 鸿蒙定位精度设置(平衡性能与精度)

&#x20;     return await Geolocator.getCurrentPosition(

&#x20;       desiredAccuracy: LocationAccuracy.medium,

&#x20;       timeLimit: const Duration(seconds: 15), // 鸿蒙定位超时保护

&#x20;     );

&#x20;   } catch (e) {

&#x20;     EasyLoading.showError('鸿蒙定位失败:\${e.toString()}');

&#x20;     return null;

&#x20;   }

&#x20; }

}

5. 状态管理:providers/weather_provider.dart(数据共享)

使用provider管理天气数据,适配鸿蒙页面重建时数据持久化:

import 'package:flutter/material.dart';

import 'package:flutter\_harmony\_weather/core/api/weather\_api.dart';

import 'package:flutter\_harmony\_weather/core/utils/location\_util.dart';

import 'package:flutter\_harmony\_weather/models/weather\_model.dart';

/// 天气状态管理类

class WeatherProvider with ChangeNotifier {

&#x20; final WeatherApi \_weatherApi = WeatherApi();

&#x20; WeatherModel? \_weatherData;

&#x20; bool \_isLoading = false;

&#x20; // 对外提供数据(只读)

&#x20; WeatherModel? get weatherData => \_weatherData;

&#x20; bool get isLoading => \_isLoading;

&#x20; /// 根据城市获取天气

&#x20; FutureByCity(String city) async {

&#x20;   \_setLoading(true);

&#x20;   try {

&#x20;     final data = await \_weatherApi.getWeatherByCity(city);

&#x20;     \_weatherData = data;

&#x20;     notifyListeners(); // 通知UI更新

&#x20;   } catch (e) {

&#x20;     // 错误已在Dio拦截器处理,此处仅恢复状态

&#x20;   } finally {

&#x20;     \_setLoading(false);

&#x20;   }

&#x20; }

&#x20; /// 根据定位获取天气

&#x20; FutureWeatherByLocation() async {

&#x20;   \_setLoading(true);

&#x20;   try {

&#x20;     Position? position = await LocationUtil.getCurrentLocation();

&#x20;     if (position != null) {

&#x20;       final data = await \_weatherApi.getWeatherByLocation(

&#x20;         position.latitude,

&#x20;         position.longitude,

&#x20;       );

&#x20;       \_weatherData = data;

&#x20;       notifyListeners();

&#x20;     }

&#x20;   } catch (e) {

&#x20;   } finally {

&#x20;     \_setLoading(false);

&#x20;   }

&#x20; }

&#x20; /// 更新加载状态

&#x20; void \_setLoading(bool loading) {

&#x20;   \_isLoading = loading;

&#x20;   if (loading) {

&#x20;     EasyLoading.show(status: '加载中...');

&#x20;   } else {

&#x20;     EasyLoading.dismiss();

&#x20;   }

&#x20;   notifyListeners();

&#x20; }

&#x20; /// 清空天气数据(退出页面时调用)

&#x20; void clearWeatherData() {

&#x20;   \_weatherData = null;

&#x20;   notifyListeners();

&#x20; }

}

6. 主页面:pages/weather_page.dart(鸿蒙 UI 适配)

优化鸿蒙屏幕适配、触摸反馈、权限引导,提升用户体验:

import 'package:flutter/material.dart';

import 'package:provider/provider.dart';

import 'package:font\_awesome\_flutter/font\_awesome\_flutter.dart';

import 'package:flutter\_harmony\_weather/providers/weather\_provider.dart';

class WeatherPage extends StatefulWidget {

&#x20; const WeatherPage({super.key});

&#x20; @override

&#x20; State createState() => \_WeatherPageState();

}

class \_WeatherPageState extends State

&#x20; final TextEditingController \_cityController = TextEditingController();

&#x20; final FocusNode \_cityFocusNode = FocusNode();

&#x20; @override

&#x20; void dispose() {

&#x20;   // 释放资源(鸿蒙页面销毁时避免内存泄漏)

&#x20;   \_cityController.dispose();

&#x20;   \_cityFocusNode.dispose();

&#x20;   Provider.of, listen: false).clearWeatherData();

&#x20;   super.dispose();

&#x20; }

&#x20; /// 隐藏键盘(鸿蒙设备触摸空白处触发)

&#x20; void \_hideKeyboard() {

&#x20;   \_cityFocusNode.unfocus();

&#x20; }

&#x20; /// 根据天气状况返回对应图标(鸿蒙图标适配)

&#x20; Widget \_getWeatherIcon(String weather) {

&#x20;   if (weather.contains('晴')) {

&#x20;     return const FaIcon(FontAwesomeIcons.sun, size: 60, color: Colors.amber);

&#x20;   } else if (weather.contains('雨')) {

&#x20;     return const FaIcon(FontAwesomeIcons.cloudRain, size: 60, color: Colors.blue);

&#x20;   } else if (weather.contains('云')) {

&#x20;     return const FaIcon(FontAwesomeIcons.cloud, size: 60, color: Colors.grey);

&#x20;   } else if (weather.contains('雪')) {

&#x20;     return const FaIcon(FontAwesomeIcons.snowflake, size: 60, color: Colors.white);

&#x20;   } else {

&#x20;     return const FaIcon(FontAwesomeIcons.cloudSun, size: 60, color: Colors.orange);

&#x20;   }

&#x20; }

&#x20; @override

&#x20; Widget build(BuildContext context) {

&#x20;   // 鸿蒙屏幕尺寸适配(获取屏幕宽度)

&#x20;   final screenWidth = MediaQuery.of(context).size.width;

&#x20;   return Scaffold(

&#x20;     appBar: AppBar(

&#x20;       title: const Text('Flutter鸿蒙天气查询'),

&#x20;       centerTitle: true,

&#x20;       // 鸿蒙沉浸式状态栏适配

&#x20;       systemOverlayStyle: SystemUiOverlayStyle(

&#x20;         statusBarColor: Colors.transparent,

&#x20;         statusBarIconBrightness: Brightness.dark,

&#x20;       ),

&#x20;     ),

&#x20;     // 触摸空白处隐藏键盘(鸿蒙用户习惯适配)

&#x20;     body: GestureDetector(

&#x20;       onTap: \_hideKeyboard,

&#x20;       child: SingleChildScrollView(

&#x20;         padding: EdgeInsets.symmetric(

&#x20;           horizontal: screenWidth \* 0.05, // 左右边距适配屏幕

&#x20;           vertical: 20,

&#x20;         ),

&#x20;         child: Column(

&#x20;           crossAxisAlignment: CrossAxisAlignment.stretch,

&#x20;           children: \[

&#x20;             // 城市输入框(鸿蒙样式优化)

&#x20;             TextField(

&#x20;               controller: \_cityController,

&#x20;               focusNode: \_cityFocusNode,

&#x20;               decoration: InputDecoration(

&#x20;                 hintText: '请输入城市名称(如:北京)',

&#x20;                 hintStyle: const TextStyle(color: Colors.grey, fontSize: 14),

&#x20;                 prefixIcon: const FaIcon(FontAwesomeIcons.city, color: Colors.blue),

&#x20;                 suffixIcon: IconButton(

&#x20;                   icon: const Icon(Icons.search, color: Colors.blue),

&#x20;                   onPressed: () {

&#x20;                     final city = \_cityController.text.trim();

&#x20;                     if (city.isNotEmpty) {

&#x20;                       Provider.ofProvider>(context, listen: false)

&#x20;                           .fetchWeatherByCity(city);

&#x20;                       \_hideKeyboard();

&#x20;                     } else {

&#x20;                       EasyLoading.showInfo('请输入城市名称');

&#x20;                     }

&#x20;                   },

&#x20;                 ),

&#x20;                 border: OutlineInputBorder(

&#x20;                   borderRadius: BorderRadius.circular(12),

&#x20;                   borderSide: const BorderSide(color: Colors.blue),

&#x20;                 ),

&#x20;                 focusedBorder: OutlineInputBorder(

&#x20;                   borderRadius: BorderRadius.circular(12),

&#x20;                   borderSide: const BorderSide(color: Colors.blue, width: 2),

&#x20;                 ),

&#x20;                 contentPadding: const EdgeInsets.symmetric(vertical: 15),

&#x20;               ),

&#x20;               style: const TextStyle(fontSize: 16),

&#x20;               textInputAction: TextInputAction.search,

&#x20;               onSubmitted: (value) {

&#x20;                 if (value.trim().isNotEmpty) {

&#x20;                   Provider.of, listen: false)

&#x20;                       .fetchWeatherByCity(value.trim());

&#x20;                 }

&#x20;               },

&#x20;             ),

&#x20;             const SizedBox(height: 20),

&#x20;             // 定位按钮(鸿蒙原生按钮样式)

&#x20;             ElevatedButton.icon(

&#x20;               onPressed: () {

&#x20;                 Provider.ofProvider>(context, listen: false)

&#x20;                     .fetchWeatherByLocation();

&#x20;               },

&#x20;               icon: const FaIcon(FontAwesomeIcons.locationCrosshairs),

&#x20;               label: const Text('定位获取当前城市天气'),

&#x20;               style: ElevatedButton.styleFrom(

&#x20;                 padding: const EdgeInsets.symmetric(vertical: 15),

&#x20;                 shape: RoundedRectangleBorder(

&#x20;                   borderRadius: BorderRadius.circular(12),

&#x20;                 ),

&#x20;                 backgroundColor: Colors.blue,

&#x20;                 foregroundColor: Colors.white,

&#x20;                 textStyle: const TextStyle(fontSize: 16),

&#x20;               ),

&#x20;             ),

&#x20;             const SizedBox(height: 30),

&#x20;             // 天气数据展示(使用Consumer监听状态变化)

&#x20;             Consumer

&#x20;               builder: (context, provider, child) {

&#x20;                 if (provider.weatherData == null) {

&#x20;                   return \_buildEmptyState();

&#x20;                 }

&#x20;                 return \_buildWeatherCard(provider.weatherData!);

&#x20;               },

&#x20;             ),

&#x20;           ],

&#x20;         ),

&#x20;       ),

&#x20;     ),

&#x20;   );

&#x20; }

&#x20; /// 空状态展示(鸿蒙友好提示)

&#x20; Widget \_buildEmptyState() {

&#x20;   return Column(

&#x20;     crossAxisAlignment: CrossAxisAlignment.center,

&#x20;     children: \[

&#x20;       const SizedBox(height: 80),

&#x20;       const FaIcon(FontAwesomeIcons.cloudQuestion, size: 80, color: Colors.grey),

&#x20;       const SizedBox(height: 20),

&#x20;       Text(

&#x20;         '暂无天气数据',

&#x20;         style: TextStyle(fontSize: 18, color: Colors.grey\[600]),

&#x20;       ),

&#x20;       const SizedBox(height: 10),

&#x20;       Text(

&#x20;         '请输入城市名称或点击定位按钮查询',

&#x20;         style: TextStyle(fontSize: 14, color: Colors.grey\[400]),

&#x20;       ),

&#x20;     ],

&#x20;   );

&#x20; }

&#x20; /// 天气卡片(鸿蒙阴影效果适配)

&#x20; Widget \_buildWeatherCard(WeatherModel weather) {

&#x20;   return Card(

&#x20;     elevation: 8, // 鸿蒙设备阴影渲染优化

&#x20;     shape: RoundedRectangleBorder(

&#x20;       borderRadius: BorderRadius.circular(16),

&#x20;     ),

&#x20;     child: Padding(

&#x20;       padding: const EdgeInsets.all(25),

&#x20;       child: Column(

&#x20;         crossAxisAlignment: CrossAxisAlignment.center,

&#x20;         children: \[

&#x20;           // 城市名称

&#x20;           Text(

&#x20;             weather.city,

&#x20;             style: const TextStyle(

&#x20;               fontSize: 28,

&#x20;               fontWeight: FontWeight.bold,

&#x20;               color: Colors.black87,

&#x20;             ),

&#x20;           ),

&#x20;           const SizedBox(height: 5),

&#x20;           // 更新时间

&#x20;           Text(

&#x20;             '更新时间:\${weather.updateTime}',

&#x20;             style: TextStyle(fontSize: 14, color: Colors.grey\[500]),

&#x20;           ),

&#x20;           const SizedBox(height: 30),

&#x20;           // 天气图标+温度

&#x20;           Row(

&#x20;             mainAxisAlignment: MainAxisAlignment.center,

&#x20;             crossAxisAlignment: CrossAxisAlignment.center,

&#x20;             children: \[

&#x20;               \_getWeatherIcon(weather.weather),

&#x20;               const SizedBox(width: 20),

&#x20;               Text(

&#x20;                 '\${weather.temp}℃',

&#x20;                 style: const TextStyle(

&#x20;                   fontSize: 60,

&#x20;                   fontWeight: FontWeight.w300,

&#x20;                   color: Colors.black87,

&#x20;                 ),

&#x20;               ),

&#x20;             ],

&#x20;           ),

&#x20;           const SizedBox(height: 15),

&#x20;           // 天气状况

&#x20;           Text(

&#x20;             weather.weather,

&#x20;             style: const TextStyle(fontSize: 22, color: Colors.black87),

&#x20;           ),

&#x20;           const SizedBox(height: 30),

&#x20;           // 风力+湿度(鸿蒙双列布局适配)

&#x20;           Row(

&#x20;             mainAxisAlignment: MainAxisAlignment.space-around,

&#x20;             children: \[

&#x20;               \_buildWeatherInfoItem(

&#x20;                 FontAwesomeIcons.wind,

&#x20;                 '风力',

&#x20;                 weather.wind,

&#x20;               ),

&#x20;               \_buildWeatherInfoItem(

&#x20;                 FontAwesomeIcons.droplet,

&#x20;                 '湿度',

&#x20;                 weather.humidity,

&#x20;               ),

&#x20;             ],

&#x20;           ),

&#x20;         ],

&#x20;       ),

&#x20;     ),

&#x20;   );

&#x20; }

&#x20; /// 天气信息子项(复用组件)

&#x20; Widget \_buildWeatherInfoItem(IconData icon, String title, String value) {

&#x20;   return Column(

&#x20;     children: \[

&#x20;       FaIcon(icon, size: 30, color: Colors.blue),

&#x20;       const SizedBox(height: 10),

&#x20;       Text(

&#x20;         title,

&#x20;         style: TextStyle(fontSize: 16, color: Colors.grey\[600]),

&#x20;       ),

&#x20;       const SizedBox(height: 5),

&#x20;       Text(

&#x20;         value,

&#x20;         style: const TextStyle(fontSize: 18, color: Colors.black87),

&#x20;       ),

&#x20;     ],

&#x20;   );

&#x20; }

}

7. 应用入口:main.dart(鸿蒙全局配置)

import 'package:flutter/material.dart';

import 'package:provider/provider.dart';

import 'package:flutter\_easyloading/flutter\_easyloading.dart';

import 'pages/weather\_page.dart';

import 'providers/weather\_provider.dart';

import 'core/constants/constants.dart';

void main() {

&#x20; runApp(

&#x20;   // 全局注入状态管理

&#x20;   ChangeNotifierProvider(

&#x20;     create: (context) => WeatherProvider(),

&#x20;     child: const MyApp(),

&#x20;   ),

&#x20; );

}

class MyApp extends StatelessWidget {

&#x20; const MyApp({super.key});

&#x20; @override

&#x20; Widget build(BuildContext context) {

&#x20;   return MaterialApp(

&#x20;     title: 'Flutter鸿蒙天气',

&#x20;     // 鸿蒙主题适配(贴近鸿蒙系统风格)

&#x20;     theme: ThemeData(

&#x20;       primarySwatch: Colors.blue,

&#x20;       scaffoldBackgroundColor: Colors.grey\[50],

&#x20;       appBarTheme: const AppBarTheme(

&#x20;         backgroundColor: Colors.white,

&#x20;         foregroundColor: Colors.black87,

&#x20;         elevation: 1,

&#x20;       ),

&#x20;       // 鸿蒙按钮样式适配

&#x20;       elevatedButtonTheme: ElevatedButtonThemeData(

&#x20;         style: ElevatedButton.styleFrom(

&#x20;           shape: RoundedRectangleBorder(

&#x20;             borderRadius: BorderRadius.circular(8),

&#x20;           ),

&#x20;         ),

&#x20;       ),

&#x20;     ),

&#x20;     home: const WeatherPage(),

&#x20;     // 配置加载提示(鸿蒙样式优化)

&#x20;     builder: EasyLoading.init(

&#x20;       builder: (context, child) {

&#x20;         return MediaQuery(

&#x20;           // 鸿蒙字体适配(跟随系统字体大小)

&#x20;           data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),

&#x20;           child: child!,

&#x20;         );

&#x20;       },

&#x20;     ),

&#x20;     debugShowCheckedModeBanner: false,

&#x20;   );

&#x20; }

}

五、鸿蒙 6.0 (API20) 运行与调试指南

1. 运行前准备

  1. 启动鸿蒙 6.0 (API20) 模拟器(DevEco Studio → Tools → Device Manager → 创建 HarmonyOS 6.0 模拟器)

  2. 替换core/constants/constants.dart中的 API 密钥(从聚合数据 / 高德地图等平台申请):

class Constants {

&#x20; // 天气API基础地址(示例:聚合数据)

&#x20; static const String WEATHER\_API\_BASE\_URL = 'http://v.juhe.cn';

&#x20; // 替换为你的API密钥

&#x20; static const String WEATHER\_API\_KEY = '你的天气API密钥';

}

2. 运行命令(终端执行)

\# 运行鸿蒙设备(自动检测已连接设备)

flutter run

\# 如需指定设备,先执行flutter devices获取设备ID,再运行:

flutter run -d 你的鸿蒙设备ID

3. 鸿蒙特有调试技巧

  • 权限调试:若定位失败,在鸿蒙设备 “设置 → 应用 → Flutter 鸿蒙天气 → 权限” 中手动开启定位权限

  • 网络调试:鸿蒙模拟器需确保 “设置 → 网络” 已连接,可通过flutter logs查看网络请求日志

  • UI 调试:使用flutter inspector查看组件布局,鸿蒙设备需开启 “USB 调试(安全设置)”

  • 日志查看:终端执行flutter logs --device 你的设备ID,过滤关键词 “Dio”“Location” 查看关键日志

六、鸿蒙 6.0 兼容性问题与解决方案

问题现象 原因分析 解决方案
应用安装失败,提示 “解析包错误” 包名与鸿蒙开发者平台不一致 修改build.gradleapplicationId与平台一致
定位权限申请无响应 鸿蒙设备未开启定位服务 引导用户开启 “设置 → 定位服务”
网络请求失败,提示 “证书错误” 鸿蒙 HTTPS 证书校验严格 在 Dio 中配置忽略证书校验(开发环境)
UI 组件变形 未适配鸿蒙多分辨率屏幕 使用MediaQuery动态计算尺寸,避免固定值
加载弹窗闪屏 Flutter 与鸿蒙原生渲染冲突 延迟弹窗显示,或使用EasyLoading最新版本

七、生产环境优化(鸿蒙上线必备)

  1. API 密钥安全:将 API 密钥迁移到鸿蒙云函数 / 后端服务,避免客户端泄露

  2. 混淆加固:在proguard-rules.pro中添加三方库混淆规则,防止反编译

  3. 性能优化

  • 图片资源压缩(适配鸿蒙设备分辨率)

  • 定位结果缓存(避免频繁调用定位 API)

  • 接口数据缓存(使用shared_preferences缓存天气数据)

  1. 鸿蒙上架适配
  • 替换应用图标为鸿蒙规范图标(尺寸:16x16、48x48、128x128)

  • 填写鸿蒙应用市场所需权限说明、隐私政策

  • 适配鸿蒙深色模式(添加ThemeData.dark()主题)

八、核心知识点总结(新手必记)

  1. 鸿蒙 6.0 适配核心targetSdkVersion 33、权限显式声明、包名一致性

  2. 三方库选型原则:优先选择 “支持 Android 13+”“维护活跃” 的库,避免小众库

  3. 开发流程规范:分层架构 → 模型定义 → 工具封装 → UI 实现 → 鸿蒙适配 → 调试上线

  4. 关键技术点

  • Dio 拦截器处理鸿蒙网络错误

  • Geolocator 适配鸿蒙定位权限

  • Provider 实现跨页面数据共享

  • JSON 序列化提升解析效率

九、拓展方向(进阶学习)

  1. 集成鸿蒙原生能力:调用鸿蒙推送、支付、地图等原生 API(通过method_channel

  2. 多语言适配:添加中文 / 英文切换,适配鸿蒙系统语言设置

  3. 离线功能:使用sqflite存储历史天气数据,支持离线查看

  4. 图表展示:集成fl_chart库,展示未来 7 天天气趋势

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐