Flutter 鸿蒙开发入门:基于三方库实现新闻列表App(实操教程)
本次教程以新闻列表App为案例,覆盖了 Flutter 鸿蒙开发中网络请求、图片加载、下拉刷新、屏幕适配等核心场景,选用的三方库均为行业高频且适配鸿蒙的版本,与上一篇的待办事项案例形成互补。拓展方向(差异化拓展):集成 `flutter_html` 库,实现新闻详情页的富文本显示;结合鸿蒙原生能力,实现新闻推送、后台刷新功能;优化UI,添加夜间模式、字体大小调整等功能。
Flutter 鸿蒙开发入门:基于三方库实现新闻列表App(实操教程)
一、前言
Flutter 跨端开发的核心优势的是“一次开发,多端部署”,而鸿蒙作为新兴的分布式操作系统,其与 Flutter 的结合能极大提升开发效率。不同于上一篇的待办事项案例,本次将以 「新闻列表App」 为载体,选用更贴合资讯类应用的三方库,讲解 Flutter 在鸿蒙平台的实践细节——包括网络请求、列表渲染、图片加载、下拉刷新等核心功能,帮助新手快速掌握 Flutter 鸿蒙开发的不同应用场景,避开常见适配坑。
本教程适合具备基础 Flutter 语法知识、刚接触鸿蒙开发的开发者,全程手把手操作,所有代码均附带详细注释,可直接复制运行。
二、环境准备(精简优化,突出鸿蒙适配重点)
2.1 核心环境要求
与上一篇基础环境一致,但重点强调鸿蒙适配的关键版本,避免版本不兼容问题:
-
Flutter:3.13+(推荐3.16,鸿蒙适配更稳定)
-
OpenHarmony SDK:4.0+(API Version 10,支持更多Flutter特性)
-
DevEco Studio:4.1+(优化了Flutter项目调试体验)
-
鸿蒙设备/模拟器:API Version 10 及以上(确保三方库正常运行)
2.2 关键环境配置(补充上一篇未提及的细节)
-
Flutter 鸿蒙支持开启(同前一篇,补充验证命令)
\# 开启鸿蒙平台支持 flutter config \-\-enable\-harmonyos \# 验证开启成功(输出中包含 harmonyos) flutter config \-\-list -
DevEco Studio 配置补充:
进入 DevEco Studio → Settings → Appearance & Behavior → System Settings → HarmonyOS SDK,勾选“HarmonyOS SDK 4.0”和“Flutter HarmonyOS Adapter”,点击下载安装,完成后重启IDE。 -
设备调试配置:
鸿蒙设备开启开发者模式后,除了USB调试,还需开启“允许调试应用”权限(设置 → 系统和更新 → 开发者选项 → 允许调试应用),否则Flutter应用无法正常安装。
三、项目创建与鸿蒙平台初始化
3.1 创建Flutter鸿蒙项目(简化命令,补充包名规范)
# 创建项目,包名遵循鸿蒙规范(com.公司名.项目名)
flutter create --org com.example.news news_harmony
cd news_harmony
3.2 鸿蒙平台适配配置(差异化配置)
- 修改 pubspec.yaml,添加鸿蒙平台依赖和权限声明:
`name: news_harmony
description: Flutter + 鸿蒙 新闻列表App
version: 1.0.0+1
environment:
sdk: '>=3.0.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
# 后续集成三方库
cupertino_icons: ^1.0.6 # 苹果风格图标(适配鸿蒙UI)
# 新增:鸿蒙平台配置
harmonyos:
# 应用图标配置(后续可替换为自定义图标)
icon:
foreground: assets/icons/app_icon.png
# 应用名称
label: 鸿蒙新闻`
- 生成鸿蒙平台目录并同步配置:
\# 生成鸿蒙平台相关目录(entry、ohosTest等) flutter create \. \-\-platforms harmonyos \# 同步pub依赖到鸿蒙项目 flutter pub get
四、三方库选型与集成(全新选型,贴合新闻场景)
本次选用4个高频三方库,覆盖新闻App核心需求,均已适配鸿蒙平台,避免适配踩坑:
-
dio:网络请求库,用于获取新闻数据(替代上一篇的数据库,聚焦网络场景)
-
cached_network_image:图片缓存加载库,优化新闻封面加载体验
-
pull_to_refresh:下拉刷新、上拉加载库,适配新闻列表交互
-
flutter_screenutil:屏幕适配库,保证不同鸿蒙设备UI一致性
4.1 集成三方库
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.6
dio: ^5.4.0 # 网络请求
cached_network_image: ^3.3.0 # 图片缓存
pull_to_refresh: ^2.0.0 # 下拉刷新
flutter_screenutil: ^5.9.0 # 屏幕适配
执行安装命令,确保所有依赖下载完成:
flutter pub get
五、核心代码实现(新闻列表完整逻辑,含差异化细节)
5.1 基础配置(屏幕适配、网络请求工具类)
5.1.1 屏幕适配初始化(main.dart 入口配置)
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'pages/news_list_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
// 初始化屏幕适配,设计稿按375x667(手机通用尺寸)
return ScreenUtilInit(
designSize: const Size(375, 667),
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return MaterialApp(
title: '鸿蒙新闻',
// 适配鸿蒙系统字体
theme: ThemeData(
fontFamily: 'HarmonyOS Sans',
primarySwatch: Colors.blue,
),
home: const NewsListPage(),
debugShowCheckedModeBanner: false, // 隐藏调试横幅
);
},
);
}
}
5.1.2 网络请求工具类(http_util.dart)
import 'package:dio/dio.dart';
// 网络请求工具类,封装GET请求(新闻列表无需POST)
class HttpUtil {
// 单例模式
static final HttpUtil instance = HttpUtil._private();
HttpUtil._private();
// 初始化Dio实例
final Dio _dio = Dio(
BaseOptions(
baseUrl: 'https://api.apiopen.top', // 免费新闻接口(公开可使用)
connectTimeout: Duration(seconds: 5), // 连接超时
receiveTimeout: Duration(seconds: 3), // 接收超时
),
);
// 获取新闻列表数据(参数:页码、每页数量)
Future<Map<String, dynamic>> getNewsList({int page = 1, int size = 10}) async {
try {
Response response = await _dio.get(
'/getWangYiNews', // 网易新闻接口(免费公开)
queryParameters: {'page': page, 'size': size},
);
// 返回接口数据(接口格式:{code:200, message:"success", result:[]})
return response.data;
} catch (e) {
// 异常处理,返回错误信息
return {'code': -1, 'message': '网络请求失败:$e'};
}
}
}
5.2 新闻模型类(news_model.dart)
// 新闻模型类,用于解析接口返回的数据
class NewsModel {
final String title; // 新闻标题
final String source; // 新闻来源(如网易新闻)
final String time; // 发布时间
final String image; // 新闻封面图片地址
final String content; // 新闻简介
// 构造方法
NewsModel({
required this.title,
required this.source,
required this.time,
required this.image,
required this.content,
});
// 从接口返回的Map数据,转换为NewsModel对象
factory NewsModel.fromMap(Map<String, dynamic> map) {
return NewsModel(
title: map['title'] ?? '无标题',
source: map['source'] ?? '未知来源',
time: map['time'] ?? '',
image: map['image'] ?? 'https://via.placeholder.com/100', // 占位图
content: map['content'] ?? '暂无内容',
);
}
}
5.3 新闻列表页面(核心页面,news_list_page.dart)
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../utils/http_util.dart';
import '../models/news_model.dart';
class NewsListPage extends StatefulWidget {
const NewsListPage({super.key});
State<NewsListPage> createState() => _NewsListPageState();
}
class _NewsListPageState extends State<NewsListPage> {
List<NewsModel> _newsList = []; // 新闻列表数据
int _currentPage = 1; // 当前页码
final int _pageSize = 10; // 每页数量
final RefreshController _refreshController = RefreshController(initialRefresh: true); // 刷新控制器
void dispose() {
// 页面销毁时释放资源
_refreshController.dispose();
super.dispose();
}
// 加载新闻数据(下拉刷新、上拉加载共用)
Future<void> _loadNewsData({bool isRefresh = true}) async {
// 下拉刷新时,重置页码为1
if (isRefresh) {
_currentPage = 1;
}
// 调用网络请求工具类,获取新闻数据
Map<String, dynamic> response = await HttpUtil.instance.getNewsList(
page: _currentPage,
size: _pageSize,
);
if (response['code'] == 200) {
// 接口请求成功,解析数据
List<dynamic> dataList = response['result'] ?? [];
List<NewsModel> newNewsList = dataList.map((e) => NewsModel.fromMap(e)).toList();
setState(() {
if (isRefresh) {
// 下拉刷新:替换原有数据
_newsList = newNewsList;
} else {
// 上拉加载:追加数据
_newsList.addAll(newNewsList);
}
});
// 刷新/加载状态更新
if (isRefresh) {
_refreshController.refreshCompleted();
} else {
// 判断是否有更多数据(如果返回数据少于每页数量,说明没有更多)
if (newNewsList.length < _pageSize) {
_refreshController.loadNoData();
} else {
_refreshController.loadComplete();
_currentPage++; // 页码加1,用于下一次加载
}
}
} else {
// 接口请求失败,提示错误
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(response['message'] ?? '加载失败')),
);
// 刷新/加载状态更新
if (isRefresh) {
_refreshController.refreshFailed();
} else {
_refreshController.loadFailed();
}
}
}
// 新闻列表项组件
Widget _newsItem(NewsModel news) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.h),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey[200], width: 1)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 新闻封面图片(缓存加载)
CachedNetworkImage(
imageUrl: news.image,
width: 100.w,
height: 80.h,
fit: BoxFit.cover,
// 加载中占位图
placeholder: (context, url) => Container(color: Colors.grey[200]),
// 加载失败占位图
errorWidget: (context, url, error) => const Icon(Icons.error),
),
SizedBox(width: 12.w),
// 新闻标题、来源、时间
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
news.title,
maxLines: 2, // 最多显示2行
overflow: TextOverflow.ellipsis, // 超出部分省略
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
news.source,
style: TextStyle(fontSize: 12.sp, color: Colors.grey[600]),
),
Text(
news.time.split(' ')[0], // 只显示日期,去掉时间
style: TextStyle(fontSize: 12.sp, color: Colors.grey[600]),
),
],
),
],
),
),
],
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'热点新闻',
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
// 下拉刷新、上拉加载列表
body: SmartRefresher(
controller: _refreshController,
enablePullDown: true, // 允许下拉刷新
enablePullUp: true, // 允许上拉加载
onRefresh: () => _loadNewsData(isRefresh: true), // 下拉刷新回调
onLoading: () => _loadNewsData(isRefresh: false), // 上拉加载回调
// 列表内容
child: ListView.builder(
itemCount: _newsList.length,
itemBuilder: (context, index) {
return _newsItem(_newsList[index]);
},
),
),
);
}
}
六、鸿蒙平台适配与运行验证(差异化重点)
6.1 鸿蒙权限配置(新增网络权限,新闻场景必需)
新闻App需要网络权限才能获取数据,修改鸿蒙项目的 module.json5 文件:
// 路径:harmonyos/entry/src/main/module.json5
{
"module": {
"name": "entry",
"type": "entry",
"description": "新闻列表App入口",
"mainElement": ".EntryAbility",
"deviceTypes": ["phone", "tablet"],
// 新增网络权限
"requestPermissions": [
{
"name": "ohos.permission.INTERNET", // 网络权限(必需)
"reason": "获取新闻数据需要访问网络",
"usedScene": {
"abilities": [".EntryAbility"],
"when": "inuse"
}
}
],
"abilities": [
{
"name": ".EntryAbility",
"srcEntry": "ets/entryability/EntryAbility.ets",
"description": "应用入口能力",
"icon": "$media:app_icon",
"label": "$string:app_name",
"visible": true,
"launchType": "standard"
}
]
}
}
6.2 运行与功能验证
-
连接鸿蒙设备或启动模拟器(确保API Version 10+);
-
执行运行命令(直接运行,无需手动构建hap包):
flutter run \-d harmonyos -
功能验证要点(与上一篇差异化):
-
首次启动:自动下拉刷新,加载第一页新闻数据;
-
下拉刷新:刷新最新新闻,验证网络请求是否正常;
-
上拉加载:滑动到底部,加载下一页新闻,验证分页功能;
-
图片加载:检查新闻封面是否正常显示,验证缓存功能;
-
屏幕适配:切换不同尺寸的鸿蒙设备/模拟器,检查UI是否正常。
-
七、常见问题与解决(新增新闻场景专属问题)
-
**网络请求失败,提示“无网络权限”**解决:检查 module.json5 中是否添加了 ohos.permission.INTERNET 权限,添加后重新运行项目;若仍失败,重启DevEco Studio和设备。
-
新闻封面图片加载失败,显示错误图标解决:检查图片地址是否有效(接口返回的图片地址可能失效),可替换为自定义占位图;同时确认设备/模拟器已连接网络。
-
下拉刷新/上拉加载无响应解决:检查 RefreshController 是否正确初始化,_loadNewsData 方法中是否调用了 refreshCompleted/loadComplete 等状态更新方法。
-
UI在不同鸿蒙设备上显示错乱解决:确保所有尺寸相关的数值(width、height、fontSize)都使用 ScreenUtil 适配(如 15.w、18.sp),避免使用固定数值。
八、总结与拓展
本次教程以新闻列表App为案例,覆盖了 Flutter 鸿蒙开发中 网络请求、图片加载、下拉刷新、屏幕适配 等核心场景,选用的三方库均为行业高频且适配鸿蒙的版本,与上一篇的待办事项案例形成互补。
拓展方向(差异化拓展):
-
集成 `flutter_html` 库,实现新闻详情页的富文本显示;
-
添加 `shared_preferences` 库,实现新闻阅读记录、收藏功能;
-
结合鸿蒙原生能力,实现新闻推送、后台刷新功能;
-
优化UI,添加夜间模式、字体大小调整等功能。
后续学习建议
如果你想继续深入,我可以帮你:
-
补充新闻详情页的完整实现代码;
-
讲解鸿蒙原生能力与Flutter的交互方法(如推送、分享);
-
优化项目性能(如图片缓存优化、网络请求拦截)。
更多推荐


所有评论(0)