news 2026/4/3 4:50:13

Flutter for HarmonyOS 开发指南(四):实现上拉加载,下拉刷新能力

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for HarmonyOS 开发指南(四):实现上拉加载,下拉刷新能力

前言

实现一个Flutter 应用中常见且核心的功能:上拉加载更多和下拉刷新。将从最基础的实现方式入手,使用 Flutter 内置的组件和控制器来构建这个功能。

核心思路

  1. 下拉刷新:使用 Flutter 官方提供的RefreshIndicator组件。它能够监听子组件的下拉手势,并在触发时执行一个回调函数,在这个回调中加载最新的数据。

  2. 上拉加载:通过监听ListViewScrollController来实现。当用户滚动到列表底部附近时,判断是否需要加载下一页数据,并执行相应的数据请求和状态更新。

完整代码

import 'package:flutter/material.dart'; // 对联数据模型 class CoupletModel { final String upper; // 上联 final String lower; // 下联 final String horizontal; // 横批 CoupletModel({ required this.upper, required this.lower, required this.horizontal, }); } class CoupletPage extends StatefulWidget { const CoupletPage({super.key}); @override State<CoupletPage> createState() => _CoupletPageState(); } class _CoupletPageState extends State<CoupletPage> { final List<CoupletModel> _dataList = []; // 数据源 final ScrollController _scrollController = ScrollController(); // 滚动控制器 int _page = 1; // 当前页码 bool _isLoading = false; // 是否正在加载 bool _hasMore = true; // 是否还有更多数据 @override void initState() { super.initState(); _refreshData(); // 初始化加载 // 监听滚动实现上拉加载 _scrollController.addListener(() { // 当滚动到距离底部小于 100 像素时,触发加载更多 if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { _loadMoreData(); } }); } @override void dispose() { _scrollController.dispose(); super.dispose(); } // 模拟网络请求方法 Future<List<CoupletModel>> _fetchMockData(int page) async { await Future.delayed(const Duration(seconds: 30)); // 模拟网络延迟 // 模拟第 4 页之后没有更多数据了 if (page > 3) return []; return List.generate(10, (index) { int id = (page - 1) * 10 + index + 1; return CoupletModel( upper: "上联:岁岁平安节节高", lower: "下联:年年如意步步升", horizontal: "横批:大吉大利", ); }); } // 下拉刷新逻辑 Future<void> _refreshData() async { setState(() { _page = 1; _hasMore = true; }); List<CoupletModel> newData = await _fetchMockData(_page); setState(() { _dataList.clear(); _dataList.addAll(newData); }); } // 加载更多逻辑 Future<void> _loadMoreData() async { if (_isLoading || !_hasMore) return; setState(() { _isLoading = true; }); int nextPage = _page + 1; List<CoupletModel> newData = await _fetchMockData(nextPage); setState(() { _isLoading = false; if (newData.isEmpty) { _hasMore = false; } else { _page = nextPage; _dataList.addAll(newData); } }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("春节对联"), backgroundColor: Colors.white, foregroundColor: Colors.black, ), body: RefreshIndicator( onRefresh: _refreshData, child: ListView.builder( controller: _scrollController, itemCount: _dataList.length + 1, // +1 是为了显示底部的加载状态 itemBuilder: (context, index) { // 如果是最后一项,根据状态显示“加载中”或“没有更多” if (index == _dataList.length) { return _buildFooter(); } final item = _dataList[index]; return _buildCoupletItem(item); }, ), ), ); } // 单个对联卡片布局 Widget _buildCoupletItem(CoupletModel item) { return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), elevation: 3, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Text( item.horizontal, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.red, ), ), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [_verticalText(item.upper), _verticalText(item.lower)], ), ], ), ), ); } // 竖排文字组件 Widget _verticalText(String text) { return SizedBox( width: 30, child: Text( text.replaceAll(":", "\n"), // 把冒号转行 style: const TextStyle( fontSize: 16, height: 1.5, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ); } // 底部加载提示 Widget _buildFooter() { return Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: _hasMore ? const CircularProgressIndicator(strokeWidth: 2) : const Text( "--- 我是有底线的 ---", style: TextStyle(color: Colors.grey), ), ), ); } }
代码实现

1. 整体页面布局 (ScaffoldAppBar)

这是页面的最外层框架,由build方法中的Scaffoldwidget构建。
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("春节对联"), backgroundColor: Colors.white, foregroundColor: Colors.black, ), body: RefreshIndicator( // ... a child widget ), ); }
  • Scaffold: 提供了标准的移动应用布局结构,包括顶部的应用栏(AppBar)和页面的主体内容(body)。

  • AppBar: 这是顶部的导航栏。

    • title: const Text("春节对联"): 设置了导航栏的标题文字。

    • backgroundColor: Colors.white: 将背景色设为白色。

    • foregroundColor: Colors.black: 将AppBar中所有前景元素(包括标题文字和默认的返回按钮图标)的颜色设为黑色,以确保在白色背景下可见。

2.下拉刷新容器 (RefreshIndicator)

页面的主体body被一个RefreshIndicator包裹,这是实现下拉刷新功能的关键。

body: RefreshIndicator( onRefresh: _refreshData, child: ListView.builder( // ... ), ),
  • RefreshIndicator: 这是一个内置的Widget,当它的子组件(child)被向下滑动到足够距离时,它会显示一个Material Design风格的刷新动画,并触发onRefresh回调。

  • onRefresh: _refreshData: 将UI手势与业务逻辑连接起来。当用户执行下拉刷新操作时,RefreshIndicator会自动调用之前定义好的_refreshData方法。

  • child:RefreshIndicator的子组件必须是可滚动的,这里放置了ListView.builder

3. 动态列表 (ListView.builder)

这是页面的核心内容区域,负责高效地显示对联列表。

child: ListView.builder( controller: _scrollController, itemCount: _dataList.length + 1, // +1 是为了显示底部的加载状态 itemBuilder: (context, index) { // 如果是最后一项,根据状态显示“加载中”或“没有更多” if (index == _dataList.length) { return _buildFooter(); } final item = _dataList[index]; return _buildCoupletItem(item); }, ),
  • ListView.builder: 这是一个高性能的列表构建器,它只会在列表项即将进入屏幕时才创建(build)它们,非常适合长列表。

  • controller: _scrollController: 将创建的滚动控制器_scrollControllerListView关联。这样就能通过监听控制器来获知列表的滚动位置,从而实现上拉加载。

  • itemCount: _dataList.length + 1: 这是实现底部加载提示的一个巧妙技巧。列表的总长度被设置为“数据列表的长度 + 1”。这个多出来的“+1”项就是用来显示“加载中”或“没有更多了”的占位符。

  • itemBuilder: 这是一个函数,用于根据索引index构建每一项的UI。

    • if (index == _dataList.length): 这是一个关键判断。当itemBuilder构建到最后一项(即那个“+1”的项)时,index正好等于数据列表的长度。这时,不渲染普通的对联卡片,而是调用_buildFooter()方法来渲染底部的加载提示。

    • else: 对于其他所有项,正常地从_dataList中取出数据,并调用_buildCoupletItem(item)来渲染一个对联卡片。

4. 底部加载提示 (_buildFooter)

用于构建列表最底部的UI,根据加载状态显示不同的内容。

Widget _buildFooter() { return Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Center( child: _hasMore ? const CircularProgressIndicator(strokeWidth: 2) : const Text( "--- 我是有底线的 ---", style: TextStyle(color: Colors.grey), ), ), ); }
  • Padding&Center: 用于提供一些垂直间距并让内容居中显示。

  • _hasMore ? ... : ...(三元运算符): 这是动态UI的核心。

    • 如果_hasMoretrue(意味着还有更多数据可以加载),则显示一个CircularProgressIndicator(圆形的加载动画)。

    • 如果_hasMorefalse(意味着所有数据都已加载完毕),则显示一段文本"--- 我是有底线的 ---"

5. 单个对联卡片 (_buildCoupletItem)

这个方法负责构建列表中每一张独立的对联卡片。

Widget _buildCoupletItem(CoupletModel item) { return Card( // ... child: Padding( // ... child: Column( children: [ Text(item.horizontal, /* ... styles ... */), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [_verticalText(item.upper), _verticalText(item.lower)], ), ], ), ), ); }
  • Card: 作为卡片的根容器,它提供了Material Design风格的圆角、阴影和背景。

  • Padding: 在Card内部添加一些边距,让内容不会紧贴卡片边缘。

  • Column: 负责将卡片内容垂直排列:横批在上方,对联在下方。

  • Text(item.horizontal, ...): 显示横批,并设置了加粗、红色、大号字体的样式。

  • SizedBox(height: 10): 在横批和对联之间创建一个固定高度的间隙。

  • Row: 负责将上下联水平排列。mainAxisAlignment: MainAxisAlignment.spaceAround让上下联在水平方向上均匀分布空间。

  • _verticalText(...):Row的子组件是两个调用_verticalText方法生成的Widget,分别用于显示上联和下联。

6. 竖排文字组件 (_verticalText)

这是一个非常巧妙的自定义Widget,用于实现文字的竖向排列效果。

Widget _verticalText(String text) { return SizedBox( width: 30, child: Text( text.replaceAll(":", "\\n"), // 把冒号转行 style: const TextStyle( fontSize: 16, height: 1.5, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ); }
  • SizedBox(width: 30, ...): 关键点之一。它强制限制了Text组件的宽度。

  • text.replaceAll(":", "\\n"):实现竖排的核心技巧。它将文本中的冒号替换为换行符\n。由于SizedBox的宽度非常窄(只能容下一个字),Text组件在渲染时会自动换行。通过将字符间的“分隔符”变成换行符,就实现了每个字占一行的效果,从而形成了视觉上的竖排。

  • style:

    • height: 1.5: 设置行高,增加了每个字之间的垂直间距,使其更美观。

    • textAlign: TextAlign.center: 确保每个字都在其狭窄的空间内居中。

效果预览

总结

通过RefreshIndicatorScrollController,用 Flutter 的原生方式实现了下拉刷新和上拉加载功能。

欢迎加入开源鸿蒙跨平台社区
https://openharmonycrossplatform.csdn.net

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/28 16:43:12

腾讯超算中心AI资源调度:架构师如何支持游戏AI应用?

腾讯超算中心AI资源调度揭秘:架构师如何为游戏AI保驾护航? 引言:游戏AI的“算力痛点”,你遇到过吗? 作为游戏开发架构师,你是否曾面临这样的困境: 想给游戏加个智能NPC(比如《王者荣耀》的AI队友),但训练10亿条对战数据需要占用100台GPU服务器,成本高得吓人; 上线…

作者头像 李华
网站建设 2026/3/27 23:25:56

超越加速:AI编程如何成为开发者能力的“无限杠杆”?

摘要&#xff1a;本文深入探讨AI编程的核心价值&#xff0c;论证其革命性意义远非简单的效率提升&#xff0c;而在于对开发者个人能力边界前所未有的“扩展”。文章通过对比“效率提升”与“能力扩展”的本质区别&#xff0c;结合多个具体技术场景&#xff08;如跨技术栈开发、…

作者头像 李华
网站建设 2026/3/24 2:05:06

设计模式——迭代器模式

迭代器模式 (Iterator Pattern) 什么是迭代器模式&#xff1f; 迭代器模式是一种行为型设计模式&#xff0c;它允许你遍历集合对象中的元素&#xff0c;而不暴露集合的内部表示。 简单来说&#xff1a;迭代器模式就是提供一个统一的接口来遍历不同的集合。 生活中的例子 想象一…

作者头像 李华
网站建设 2026/3/27 0:01:12

设计模式——适配器模式

适配器模式 (Adapter Pattern) 什么是适配器模式&#xff1f; 适配器模式是一种结构型设计模式&#xff0c;它允许不兼容的接口一起工作。适配器模式充当两个不兼容接口之间的桥梁。 简单来说&#xff1a;适配器模式就像一个转换器&#xff0c;让不兼容的接口能够协同工作。…

作者头像 李华
网站建设 2026/4/3 3:08:26

SpringBoot+Vue 高校教师电子名片系统管理平台源码【适合毕设/课设/学习】Java+MySQL

摘要 在数字化教育快速发展的背景下&#xff0c;高校教师的信息管理需求日益增长&#xff0c;传统的纸质名片和分散的教师信息管理方式已无法满足高效、便捷的沟通需求。高校教师电子名片系统管理平台通过整合教师基本信息、教学科研成果、联系方式等数据&#xff0c;实现教师信…

作者头像 李华