Flutter for OpenHarmony高级闹钟App实战:加载动画实现
本文介绍了Flutter中三种加载动画的实现方案:基础圆形加载指示器、全屏加载遮罩和按钮加载状态。基础加载组件采用StatelessWidget设计,通过可选参数实现灵活配置;全屏遮罩使用Stack布局叠加半透明背景,配合Card容器展示加载状态;按钮加载状态则通过isLoading控制显示加载指示器或文本。这些方案通过流畅动画和合理提示,有效缓解用户等待焦虑,提升应用体验。实现细节包括参数默认值
加载动画是应用中不可或缺的反馈机制,当数据加载或操作处理时,通过动画告诉用户"正在进行中,请稍候"。说实话,一个好的加载动画不仅能缓解用户等待的焦虑,还能提升应用的品质感。
咱们这次要实现多种加载动画,包括圆形进度指示器、全屏加载遮罩、按钮加载状态等。做这个功能的时候,我一直在想怎么让等待变得不那么无聊,最后决定用流畅的动画配合合理的提示,让加载过程也成为良好体验的一部分。
基础加载指示器组件
实现最常用的圆形加载指示器。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class LoadingIndicator extends StatelessWidget {
final String? message;
final double? size;
final Color? color;
const LoadingIndicator({
super.key,
this.message,
this.size,
组件参数:message是加载提示文字,size是指示器大小,color是指示器颜色,都是可选参数。
可选设计:参数都用?标记为可选,这样组件更灵活,可以只显示指示器,也可以配合文字和自定义颜色。
StatelessWidget:加载指示器本身不需要管理状态,动画由CircularProgressIndicator内部处理,所以用StatelessWidget。
通用组件:这个组件可以在多个地方使用,数据加载、操作处理、页面跳转等场景。
this.color,
});
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: size ?? 40.w,
height: size ?? 40.w,
Center居中:用Center让加载指示器在屏幕中央,这是加载动画的标准位置。
Column布局:用Column垂直排列指示器和提示文字,mainAxisAlignment.center让内容垂直居中。
mainAxisSize:设为min,让Column只占用必要的高度,不会拉伸填满整个屏幕。
SizedBox限制:用SizedBox限制CircularProgressIndicator的大小,默认40.w,也可以通过size参数自定义。
空值合并:用??运算符提供默认值,如果size为null,使用40.w作为默认大小。
child: CircularProgressIndicator(
strokeWidth: 3.w,
valueColor: color != null
? AlwaysStoppedAnimation<Color>(color!)
: null,
),
),
if (message != null) ...[
SizedBox(height: 16.h),
Text(
message!,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
),
textAlign: TextAlign.center,
),
],
],
),
);
}
}
CircularProgressIndicator:Flutter提供的圆形进度指示器,自动旋转动画,不需要手动控制。
strokeWidth:设置进度条的粗细为3.w,不要太粗也不要太细,视觉上刚好。
valueColor:如果传入了color参数,使用AlwaysStoppedAnimation设置颜色,否则使用默认颜色。
条件渲染:用if判断message是否为null,如果有提示文字就显示,没有就不显示。
文字样式:用14.sp的浅灰色显示提示文字,比如"加载中…"、“正在保存…”。字号小,颜色淡,是辅助信息。
文字居中:textAlign设为center,让文字居中对齐,与整体居中的布局保持一致。
全屏加载遮罩
实现覆盖整个屏幕的加载遮罩。
class LoadingOverlay extends StatelessWidget {
final bool isLoading;
final Widget child;
final String? message;
const LoadingOverlay({
super.key,
required this.isLoading,
required this.child,
this.message,
});
Widget build(BuildContext context) {
return Stack(
children: [
child,
Stack布局:用Stack把加载遮罩叠加在内容上方,遮罩在最上层,内容在下层。
isLoading控制:用bool变量控制是否显示加载遮罩,true显示,false隐藏。
child参数:child是被遮罩的内容,通常是整个页面或某个区域。
message参数:可选的加载提示文字,显示在加载指示器下方。
if (isLoading)
Container(
color: Colors.black54,
child: Center(
child: Card(
child: Padding(
padding: EdgeInsets.all(24.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(),
if (message != null) ...[
条件显示:用if判断isLoading,只有为true时才显示遮罩,避免不必要的渲染。
半透明背景:Container设置Colors.black54(半透明黑色),让用户知道界面被锁定,不能操作。
Card容器:用Card包裹加载指示器和文字,形成一个白色的卡片,与半透明背景形成对比。
Padding内边距:设置24.w的内边距,让加载指示器和文字不会紧贴Card边缘。
Column布局:mainAxisSize设为min,让Column只占用必要的高度,形成紧凑的加载提示框。
SizedBox(height: 16.h),
Text(
message!,
style: TextStyle(fontSize: 14.sp),
),
],
],
),
),
),
),
),
],
);
}
}
间距控制:指示器和文字之间用16.h的SizedBox分隔,形成清晰的视觉分组。
文字样式:用14.sp的默认颜色显示提示文字,在白色Card上清晰可见。
禁止交互:半透明背景会阻止用户点击下层内容,实现界面锁定效果。
使用示例:用LoadingOverlay包裹页面内容,通过isLoading控制显示隐藏,非常方便。
按钮加载状态
实现按钮的加载状态。
class LoadingButton extends StatelessWidget {
final bool isLoading;
final String text;
final VoidCallback? onPressed;
final Color? color;
const LoadingButton({
super.key,
required this.isLoading,
required this.text,
this.onPressed,
this.color,
});
Widget build(BuildContext context) {
return ElevatedButton(
isLoading控制:用bool变量控制按钮是否处于加载状态,true显示加载指示器,false显示文字。
text参数:按钮的文字,比如"保存"、“提交”、"登录"等。
onPressed回调:按钮点击回调,加载时会被禁用,避免重复提交。
color参数:可选的按钮颜色,可以自定义按钮的主题色。
onPressed: isLoading ? null : onPressed,
style: color != null
? ElevatedButton.styleFrom(backgroundColor: color)
: null,
child: isLoading
? SizedBox(
width: 20.w,
height: 20.w,
child: const CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
禁用按钮:isLoading为true时,onPressed设为null,按钮自动禁用,变灰且不可点击。
自定义颜色:如果传入了color,使用styleFrom设置backgroundColor,否则使用默认颜色。
加载指示器:isLoading为true时,child显示CircularProgressIndicator,大小20x20,白色。
strokeWidth:设为2,比默认的细一些,因为按钮空间有限,太粗会显得拥挤。
白色指示器:valueColor设为白色,因为按钮背景通常是深色,白色指示器更清晰。
);
}
}
// 使用示例
LoadingButton(
isLoading: controller.isLoading.value,
text: '保存',
onPressed: () => controller.save(),
color: Colors.blue,
)
文字显示:isLoading为false时,child显示Text(text),正常的按钮文字。
状态切换:通过改变isLoading的值,按钮在加载和正常状态之间切换,UI自动更新。
使用简单:只需要传入isLoading、text和onPressed,就能实现完整的加载按钮功能。
响应式状态:配合GetX的Rx类型,isLoading.value变化时,按钮自动重建,无需手动setState。
列表加载状态
实现列表数据加载的状态管理。
enum LoadingState {
idle, // 空闲状态
loading, // 加载中
success, // 加载成功
error, // 加载失败
}
class ListLoadingWidget extends StatelessWidget {
final LoadingState state;
final String? errorMessage;
final VoidCallback? onRetry;
const ListLoadingWidget({
super.key,
required this.state,
枚举状态:用enum定义四种加载状态,idle、loading、success、error,清晰明确。
state参数:当前的加载状态,根据状态显示不同的UI。
errorMessage:加载失败时的错误信息,显示给用户。
onRetry回调:加载失败时的重试回调,让用户可以重新加载。
this.errorMessage,
this.onRetry,
});
Widget build(BuildContext context) {
switch (state) {
case LoadingState.loading:
return const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
);
switch语句:根据state的值,返回不同的Widget,代码清晰易维护。
loading状态:显示CircularProgressIndicator,居中显示,上下左右各16像素内边距。
简洁设计:loading状态只显示指示器,不显示文字,因为用户知道正在加载。
case LoadingState.error:
return Center(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.error_outline, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(
errorMessage ?? '加载失败',
style: const TextStyle(fontSize: 14),
textAlign: TextAlign.center,
),
if (onRetry != null) ...[
const SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: const Text('重试'),
),
],
],
),
),
);
error状态:显示错误图标、错误信息和重试按钮,让用户知道出错了,并提供解决方案。
错误图标:用error_outline图标,红色,48像素大小,醒目地提示用户出错了。
错误信息:显示errorMessage,如果为null,显示默认的"加载失败"。
重试按钮:如果提供了onRetry回调,显示重试按钮,让用户可以重新加载数据。
友好提示:不仅告诉用户出错了,还提供了重试的方法,用户体验更好。
case LoadingState.idle:
case LoadingState.success:
return const SizedBox.shrink();
}
}
}
idle和success:这两种状态不需要显示任何内容,返回SizedBox.shrink(),占用0空间。
SizedBox.shrink:创建一个0x0大小的Widget,比Container()更高效,专门用于不显示内容的场景。
状态完整:四种状态都有对应的UI,覆盖了列表加载的所有场景。
下拉刷新加载
实现下拉刷新功能。
RefreshIndicator(
onRefresh: () async {
await controller.refreshData();
},
child: ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(controller.items[index].title),
);
},
),
)
RefreshIndicator:Flutter提供的下拉刷新组件,用户下拉时显示加载指示器。
onRefresh回调:返回Future,执行异步加载操作,完成后RefreshIndicator自动隐藏。
async/await:用async/await处理异步操作,代码清晰易读。
ListView包裹:RefreshIndicator必须包裹可滚动的Widget,通常是ListView或GridView。
自动隐藏:onRefresh完成后,RefreshIndicator自动隐藏,不需要手动控制。
上拉加载更多
实现列表滚动到底部时加载更多数据。
class LoadMoreListView extends StatefulWidget {
final List items;
final Future<void> Function() onLoadMore;
final Widget Function(BuildContext, int) itemBuilder;
const LoadMoreListView({
super.key,
required this.items,
required this.onLoadMore,
required this.itemBuilder,
});
State<LoadMoreListView> createState() => _LoadMoreListViewState();
}
class _LoadMoreListViewState extends State<LoadMoreListView> {
final ScrollController _scrollController = ScrollController();
bool _isLoadingMore = false;
StatefulWidget:需要管理加载状态和滚动监听,所以用StatefulWidget。
items参数:列表数据,从外部传入。
onLoadMore回调:加载更多数据的回调,返回Future。
itemBuilder:列表项的构建函数,与ListView.builder的itemBuilder相同。
ScrollController:监听滚动事件,判断是否滚动到底部。
_isLoadingMore:标记是否正在加载更多,避免重复加载。
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
if (!_isLoadingMore) {
_loadMore();
}
}
}
Future<void> _loadMore() async {
setState(() => _isLoadingMore = true);
await widget.onLoadMore();
setState(() => _isLoadingMore = false);
}
添加监听:initState中给ScrollController添加滚动监听。
滚动判断:当滚动位置距离底部小于200像素时,触发加载更多。
防重复:用_isLoadingMore标记,避免在加载过程中重复触发。
状态更新:加载前后调用setState更新_isLoadingMore,驱动UI更新。
异步加载:用async/await执行onLoadMore回调,等待加载完成。
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return ListView.builder(
controller: _scrollController,
itemCount: widget.items.length + 1,
itemBuilder: (context, index) {
if (index < widget.items.length) {
return widget.itemBuilder(context, index);
} else {
return _isLoadingMore
? const Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
)
: const SizedBox.shrink();
}
},
);
}
}
资源释放:dispose中释放ScrollController,避免内存泄漏。
itemCount:列表项数量加1,最后一项用于显示加载指示器。
条件渲染:如果index小于items.length,显示正常的列表项,否则显示加载指示器。
加载指示器:_isLoadingMore为true时显示CircularProgressIndicator,false时显示SizedBox.shrink()。
完整功能:实现了滚动监听、自动加载、状态管理、UI更新的完整流程。
骨架屏加载
实现骨架屏加载效果。
class SkeletonLoader extends StatelessWidget {
final int itemCount;
const SkeletonLoader({
super.key,
this.itemCount = 5,
});
Widget build(BuildContext context) {
return ListView.builder(
itemCount: itemCount,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
_buildSkeleton(60, 60, isCircle: true),
const SizedBox(width: 16),
itemCount参数:骨架屏显示的项数,默认5个,可以自定义。
ListView.builder:用ListView.builder创建多个骨架项,模拟真实列表的结构。
Padding内边距:每个骨架项设置16像素内边距,与真实列表项保持一致。
Row布局:用Row横向排列圆形头像和文字区域,模拟列表项的布局。
_buildSkeleton:自定义方法,创建骨架占位块,isCircle参数控制是否是圆形。
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSkeleton(double.infinity, 16),
const SizedBox(height: 8),
_buildSkeleton(200, 14),
],
),
),
],
),
);
},
);
}
Widget _buildSkeleton(double width, double height, {bool isCircle = false}) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: isCircle ? null : BorderRadius.circular(4),
shape: isCircle ? BoxShape.circle : BoxShape.rectangle,
),
);
}
}
Expanded扩展:文字区域用Expanded占据剩余空间,与真实布局一致。
Column布局:用Column垂直排列标题和副标题的骨架块。
不同尺寸:标题用double.infinity宽度,高度16;副标题用200宽度,高度14,模拟真实文字的大小。
_buildSkeleton方法:创建灰色的占位块,width和height控制大小,isCircle控制形状。
圆形头像:isCircle为true时,shape设为BoxShape.circle,创建圆形骨架。
圆角矩形:isCircle为false时,用BorderRadius.circular(4)创建圆角矩形。
颜色选择:用Colors.grey[300],浅灰色,与背景色对比不要太强,避免刺眼。
总结
加载动画是应用中重要的反馈机制,通过流畅的动画和清晰的提示,让用户知道操作正在进行中。从简单的圆形指示器到复杂的骨架屏,从按钮加载到全屏遮罩,不同场景需要不同的加载方式。
说实话,做加载动画让我对用户体验有了更深的理解。加载不可避免,但可以让等待变得不那么无聊。动画要流畅,不要卡顿,否则会让用户更焦虑。提示要清晰,告诉用户在加载什么,还需要多久。状态要管理好,避免加载状态混乱。交互要禁用,加载时不能让用户重复操作。
如果你也在做加载功能,建议重点关注动画的流畅性和提示的清晰性。动画要简洁,不要过度复杂,避免影响性能。提示要有用,告诉用户具体信息,而不是只说"加载中"。状态要完整,idle、loading、success、error都要考虑。错误要处理,提供重试按钮,给用户解决问题的机会。骨架屏要逼真,模拟真实内容的布局,让用户有心理预期。
欢迎加入OpenHarmony跨平台开发社区交流:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)