核心内容摘要
论文AI率从90%降到10%怎么做?3招搞定不伤原意
前言实现一个Flutter 应用中常见且核心的功能上拉加载更多和下拉刷新。
将从最基础的实现方式入手使用 Flutter 内置的组件和控制器来构建这个功能。
核心思路下拉刷新使用 Flutter 官方提供的RefreshIndicator组件。
它能够监听子组件的下拉手势并在触发时执行一个回调函数在这个回调中加载最新的数据。
上拉加载通过监听ListView的ScrollController来实现。
当用户滚动到列表底部附近时判断是否需要加载下一页数据并执行相应的数据请求和状态更新。
完整代码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 StateCoupletPage createState() _CoupletPageState(); } class _CoupletPageState extends StateCoupletPage { final ListCoupletModel _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 -
{ _loadMoreData(); } }); } override void dispose() { _scrollController.dispose(); super.dispose(); } // 模拟网络请求方法 FutureListCoupletModel _fetchMockData(int page) async { await Future.delayed(const Duration(seconds:
); // 模拟网络延迟 // 模拟第 4 页之后没有更多数据了 if (page
return []; return List.generate(10, (index) { int id (page -
* 10 index 1; return CoupletModel( upper: 上联岁岁平安节节高, lower: 下联年年如意步步升, horizontal: 横批大吉大利, ); }); } // 下拉刷新逻辑 Futurevoid _refreshData() async { setState(() { _page 1; _hasMore true; }); ListCoupletModel newData await _fetchMockData(_page); setState(() { _dataList.clear(); _dataList.addAll(newData); }); } // 加载更多逻辑 Futurevoid _loadMoreData() async { if (_isLoading || !_hasMore) return; setState(() { _isLoading true; }); int nextPage _page 1; ListCoupletModel 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:
, elevation: 3, child: Padding( padding: const EdgeInsets.all(
16.
, child: Column( children: [ Text( item.horizontal, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 18, color: Colors.red, ), ), const SizedBox(height:
, 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:
5, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ); } // 底部加载提示 Widget _buildFooter() { return Padding( padding: const EdgeInsets.symmetric(vertical:
, child: Center( child: _hasMore ? const CircularProgressIndicator(strokeWidth:
: const Text( --- 我是有底线的 ---, style: TextStyle(color: Colors.grey), ), ), ); } }代码实现
整体页面布局 (Scaffold和AppBar)这是页面的最外层框架由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中所有前景元素包括标题文字和默认的返回按钮图标的颜色设为黑色以确保在白色背景下可见。
下拉刷新容器 (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。
动态列表 (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: 将创建的滚动控制器_scrollController与ListView关联。
这样就能通过监听控制器来获知列表的滚动位置从而实现上拉加载。
itemCount: _dataList.length 1: 这是实现底部加载提示的一个巧妙技巧。
列表的总长度被设置为“数据列表的长度 1”。
这个多出来的“1”项就是用来显示“加载中”或“没有更多了”的占位符。
itemBuilder: 这是一个函数用于根据索引index构建每一项的UI。
if (index _dataList.length): 这是一个关键判断。
当itemBuilder构建到最后一项即那个“1”的项时index正好等于数据列表的长度。
这时不渲染普通的对联卡片而是调用_buildFooter()方法来渲染底部的加载提示。
else: 对于其他所有项正常地从_dataList中取出数据并调用_buildCoupletItem(item)来渲染一个对联卡片。
底部加载提示 (_buildFooter)用于构建列表最底部的UI根据加载状态显示不同的内容。
Widget _buildFooter() { return Padding( padding: const EdgeInsets.symmetric(vertical:
, child: Center( child: _hasMore ? const CircularProgressIndicator(strokeWidth:
: const Text( --- 我是有底线的 ---, style: TextStyle(color: Colors.grey), ), ), ); }PaddingCenter: 用于提供一些垂直间距并让内容居中显示。
_hasMore ? ... : ...(三元运算符): 这是动态UI的核心。
如果_hasMore为true意味着还有更多数据可以加载则显示一个CircularProgressIndicator圆形的加载动画。
如果_hasMore为false意味着所有数据都已加载完毕则显示一段文本--- 我是有底线的 ---。
单个对联卡片 (_buildCoupletItem)这个方法负责构建列表中每一张独立的对联卡片。
Widget _buildCoupletItem(CoupletModel item) { return Card( // ... child: Padding( // ... child: Column( children: [ Text(item.horizontal, /* ... styles ... */), const SizedBox(height:
, Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [_verticalText(item.upper), _verticalText(item.lower)], ), ], ), ), ); }Card: 作为卡片的根容器它提供了Material Design风格的圆角、阴影和背景。
Padding: 在Card内部添加一些边距让内容不会紧贴卡片边缘。
Column: 负责将卡片内容垂直排列横批在上方对联在下方。
Text(item.horizontal, ...): 显示横批并设置了加粗、红色、大号字体的样式。
SizedBox(height:
: 在横批和对联之间创建一个固定高度的间隙。
Row: 负责将上下联水平排列。
mainAxisAlignment: MainAxisAlignment.spaceAround让上下联在水平方向上均匀分布空间。
_verticalText(...):Row的子组件是两个调用_verticalText方法生成的Widget分别用于显示上联和下联。
竖排文字组件 (_verticalText)这是一个非常巧妙的自定义Widget用于实现文字的竖向排列效果。
Widget _verticalText(String text) { return SizedBox( width: 30, child: Text( text.replaceAll(, \\n), // 把冒号转行 style: const TextStyle( fontSize: 16, height:
5, fontWeight: FontWeight.w500, ), textAlign: TextAlign.center, ), ); }SizedBox(width: 30, ...): 关键点之一。
它强制限制了Text组件的宽度。
text.replaceAll(, \\n):实现竖排的核心技巧。
它将文本中的冒号替换为换行符\n。
由于SizedBox的宽度非常窄只能容下一个字Text组件在渲染时会自动换行。
通过将字符间的“分隔符”变成换行符就实现了每个字占一行的效果从而形成了视觉上的竖排。
style:height:
5: 设置行高增加了每个字之间的垂直间距使其更美观。
textAlign: TextAlign.center: 确保每个字都在其狭窄的空间内居中。
效果预览
总结通过RefreshIndicator和ScrollController用 Flutter 的原生方式实现了下拉刷新和上拉加载功能。
欢迎加入开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.net