本阶段将带你跨越“单机应用”,实现与服务器的数据交互:

  1. Mock 服务:使用 Node.js 搭建支持分页逻辑的 RESTful API。

  2. 高效列表:集成 pull_to_refresh 实现下拉刷新与触底加载。

  3. 联调避坑:解决鸿蒙真机环境下局域网访问与权限配置问题。


一、 后端基座:Node.js 分页接口搭建

在开发初期,一个可控的本地后端能极大提升联调效率。

1. 服务初始化

创建 local_server 目录,安装核心依赖:

npm init -y
npm install express cors

2. 核心逻辑 (server.js)

这里增加了一个简单的数据生成器,确保有足够的分页测试数据。

JavaScript

const express = require('express');
const cors = require('cors');
const app = express();

app.use(cors());
app.use(express.json());

// 生成 50 条测试数据
const allMemos = Array.from({ length: 50 }, (v, k) => ({
    id: k + 1,
    title: `备忘录事项 ${k + 1}`,
    content: `这是来自 Node.js 后端的测试数据内容,编号为 ${k + 1}`,
    time: new Date().toLocaleString()
}));

app.get('/api/memos', (req, res) => {
    const page = parseInt(req.query.page) || 1;
    const pageSize = parseInt(req.query.pageSize) || 10;
    const startIndex = (page - 1) * pageSize;
    const results = allMemos.slice(startIndex, startIndex + pageSize);

    res.json({
        code: 200,
        data: results,
        total: allMemos.length,
        totalPages: Math.ceil(allMemos.length / pageSize)
    });
});

app.listen(3000, '0.0.0.0', () => {
    console.log('✅ 服务已启动: http://<你的电脑IP>:3000');
});

二、 前端攻坚:实现列表双向刷新

1. 数据模型定义

在 Flutter 中,先定义好对应的 Memo 模型,便于类型安全。

class Memo {
  final int id;
  final String title;
  final String content;

  Memo.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        title = json['title'],
        content = json['content'];
}

2. 状态管理与刷新控制

使用 SmartRefresher 是目前鸿蒙适配较好的选择。

// 核心刷新函数
Future<void> _onLoading({bool isRefresh = false}) async {
  if (isRefresh) _currentPage = 1;
  
  try {
    final response = await http.get(Uri.parse("$baseUrl?page=$_currentPage&pageSize=10"));
    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      List<Memo> fetchedMemos = (data['data'] as List).map((i) => Memo.fromJson(i)).toList();
      
      setState(() {
        if (isRefresh) {
          _memos = fetchedMemos;
          _refreshController.refreshCompleted();
        } else {
          _memos.addAll(fetchedMemos);
          _refreshController.loadComplete();
        }
      });
      
      if (fetchedMemos.length < 10) _refreshController.loadNoData();
      _currentPage++;
    }
  } catch (e) {
    isRefresh ? _refreshController.refreshFailed() : _refreshController.loadFailed();
  }
}

三、 鸿蒙真机联调关键配置 (必看)

很多开发者在这一步会遇到 Network Error,请务必检查以下三点:

1. 申请网络权限

打开 ohos/entry/src/main/module.json5,确保包含权限声明:

代码段

"requestPermissions": [
  { "name": "ohos.permission.INTERNET" },
  { "name": "ohos.permission.GET_NETWORK_INFO" }
]

2. 确定 BaseURL

  • 模拟器:可以使用 10.0.2.2 或电脑 IP。

  • 真机严禁使用 localhost。必须使用电脑的局域网 IP(如 192.168.x.x),且手机与电脑必须在同一 Wi-Fi 下。

3. 关闭防火墙

如果 IP 正确但仍无法访问,请尝试关闭电脑防火墙,或放行 3000 端口。


四、 成果演示

下拉刷新 (Pull) 上拉加载 (Loading) 数据耗尽 (End)
头部显示水滴/圆圈,列表重置 底部加载动画,数据无缝拼接 底部显示“没有更多数据”

当然可以!针对 DAY 4-6 进阶课程中关于“Flutter 与后端数据交互及分页逻辑”的核心知识点,我为你准备了一套更具鸿蒙特色的 UI 样式和技术升级建议。

以下是针对你提到的 2. 提供一套针对鸿蒙风格(HarmonyOS Design)定制的列表 UI 样式代码 的详细实现:


🎨 鸿蒙风格(HarmonyOS Style)列表 UI 实现

在鸿蒙生态中,视觉体验强调“轻盈、有活力、秩序感”。我们可以通过修改 ListView 的 Item 样式,使其更符合 HarmonyOS 设计规范

  • 圆角卡片:使用 8px12px 的大圆角。

  • 层级阴影:微弱的投影,增加悬浮感。

  • 字体排版:标题与正文有明显的字号和颜色对比。

1. 鸿蒙风格卡片组件 (MemoCard)

Dart

import 'package:flutter/material.dart';

class MemoCard extends StatelessWidget {
  final String title;
  final String content;
  final String time;

  const MemoCard({required this.title, required this.content, required this.time});

  @override
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(16), // 鸿蒙典型大圆角
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.05),
            blurRadius: 10,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                title,
                style: const TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF1A1A1A), // 深色文字
                ),
              ),
              const Icon(Icons.arrow_forward_ios, size: 14, color: Colors.grey),
            ],
          ),
          const SizedBox(height: 8),
          Text(
            content,
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              fontSize: 14,
              color: Colors.black.withOpacity(0.6),
              height: 1.5,
            ),
          ),
          const SizedBox(height: 12),
          Divider(color: Colors.grey.withOpacity(0.1)),
          const SizedBox(height: 4),
          Text(
            time,
            style: const TextStyle(fontSize: 12, color: Colors.grey),
          ),
        ],
      ),
    );
  }
}

🏗️ 分页请求逻辑示意图

为了帮你理清 _onRefresh_onLoading 在整个生命周期中的作用,这张图展示了数据流转过程:


🛠️ 性能与体验优化技巧

在实现双向刷新时,还有几个提升“专业感”的小细节:

A. 骨架屏 (Skeleton Screen)

当第一次加载数据或下拉刷新时,不要只显示一个转圈圈,可以使用 shimmer 插件实现骨架屏,提升用户感知的加载速度。

B. 空状态处理

当搜索无结果或服务器没数据时,返回一个优雅的空状态图:

Dart

child: _memos.isEmpty 
  ? Center(child: Column(children: [Image.asset('assets/empty.png'), Text("暂无备忘录")]))
  : ListView.builder(...)

C. 鸿蒙系统特有的网络配置 (Reminder)

ohos/entry/src/main/module.json5 中,除了权限,如果你使用的是 HTTP 而非 HTTPS,可能需要配置:

代码段

"metadata": [
  {
    "name": "enable_manifest_network_config",
    "value": "true"
  }
]

并配置 network_config.json 允许明文传输(虽然 Flutter 引擎通常内部处理,但在原生混合开发时这是个常见坑)。


五、 总结与作业

通过这三天的进阶实战,我们打通了本地全栈开发流程。这不仅是关于一个列表的刷新,更是掌握了 Flutter 与鸿蒙原生系统之间网络权限调用的深度配合。


Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐