180 lines
6.0 KiB
Dart
180 lines
6.0 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hgdj/assets_tool/app_colors.dart';
|
|
import 'package:pull_to_refresh/pull_to_refresh.dart';
|
|
|
|
// 回弹弹簧:临界阻尼(≈2√(stiffness·mass)=34.6) → 松手快速收回不过冲
|
|
// 注:实测此调优手感优于 3.16.8 包默认(mass2.2/stiffness150/damping16,欠阻尼、回弹更夸张),
|
|
// 故采用这套调优值,不复刻 3.16.8 原值
|
|
// 提成常量供横向的 HorizontalLoadMore 复用,两处手感必须一致,改这里即全改
|
|
const SpringDescription kRefreshSpring =
|
|
SpringDescription(mass: 1.5, stiffness: 200, damping: 35);
|
|
const double kRefreshDragSpeedRatio = 0.91;
|
|
|
|
Widget pullYsRefresh({
|
|
required RefreshViewOnInit onInit,
|
|
required Widget child,
|
|
Key? key,
|
|
final Function(RefreshController? ctr)? onRefresh,
|
|
final Function(RefreshController? ctr)? onLoading,
|
|
bool enablePullUp = true,
|
|
bool enablePullDown = true,
|
|
String noDataText = "—— 没有更多数据了 ——",
|
|
Widget? customHeader,
|
|
Widget? footerHeader,
|
|
}) {
|
|
return CustomRefreshView(
|
|
onInit: onInit,
|
|
onLoading: onLoading,
|
|
onRefresh: onRefresh,
|
|
customHeader: customHeader,
|
|
footerHeader: footerHeader,
|
|
enablePullDown: enablePullDown,
|
|
enablePullUp: enablePullUp,
|
|
noDataText: noDataText,
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
typedef RefreshViewOnInit = Function(RefreshController controller);
|
|
|
|
class CustomRefreshView extends StatefulWidget {
|
|
final bool enablePullDown;
|
|
final bool enablePullUp;
|
|
final Function(RefreshController? ctr)? onRefresh;
|
|
final Function(RefreshController? ctr)? onLoading;
|
|
final Widget? customHeader;
|
|
final Widget? footerHeader;
|
|
final Widget child;
|
|
final String noDataText;
|
|
final RefreshViewOnInit onInit;
|
|
|
|
const CustomRefreshView({
|
|
super.key,
|
|
required this.child,
|
|
required this.onInit,
|
|
this.enablePullDown = true,
|
|
this.enablePullUp = true,
|
|
this.onRefresh,
|
|
this.onLoading,
|
|
this.customHeader,
|
|
this.footerHeader,
|
|
this.noDataText = "—— 没有更多数据了 ——",
|
|
});
|
|
|
|
@override
|
|
State<CustomRefreshView> createState() => _CustomIRefreshViewState();
|
|
}
|
|
|
|
class _CustomIRefreshViewState extends State<CustomRefreshView> {
|
|
final refreshCtr = RefreshController();
|
|
|
|
@override
|
|
void initState() {
|
|
widget.onInit(refreshCtr);
|
|
super.initState();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
//组件创建的 controller 由组件释放(谁创建谁释放)
|
|
refreshCtr.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return RefreshConfiguration(
|
|
springDescription: kRefreshSpring,
|
|
dragSpeedRatio: kRefreshDragSpeedRatio,
|
|
maxOverScrollExtent: 60,
|
|
maxUnderScrollExtent: 0,
|
|
enableBallisticRefresh: false, // 下拉不靠惯性触发刷新
|
|
enableBallisticLoad: true, // 上拉滑到底自动加载
|
|
//去掉 StretchingOverscrollIndicator,避免与下拉刷新冲突
|
|
child: ScrollConfiguration(
|
|
behavior: NoStretchScrollBehavior(),
|
|
child: SmartRefresher(
|
|
enablePullDown: widget.enablePullDown,
|
|
enablePullUp: widget.enablePullUp,
|
|
controller: refreshCtr,
|
|
onRefresh: () => widget.onRefresh?.call(refreshCtr),
|
|
onLoading: () => widget.onLoading?.call(refreshCtr),
|
|
header: widget.customHeader ??
|
|
WaterDropHeader(
|
|
waterDropColor: AppColors.actionRed,
|
|
// iOS 刷新菊花用主题色;Android 保持默认 loading
|
|
refresh: Platform.isIOS
|
|
? const CupertinoActivityIndicator(
|
|
color: AppColors.actionRed)
|
|
: null,
|
|
complete: const Text(
|
|
"刷新完成!",
|
|
style: TextStyle(
|
|
color: AppColors.tipTextColor99, fontSize: 12),
|
|
),
|
|
failed: const Text(
|
|
"刷新失敗!",
|
|
style: TextStyle(
|
|
color: AppColors.tipTextColor99, fontSize: 12),
|
|
),
|
|
),
|
|
footer: widget.footerHeader ??
|
|
ClassicFooter(
|
|
loadingText: "加载中...",
|
|
canLoadingText: "松开加载更多...",
|
|
noDataText: widget.noDataText,
|
|
idleText: "上拉加载更多",
|
|
// Android 默认转圈是蓝色,改成主题色;iOS 保持菊花
|
|
loadingIcon: SizedBox(
|
|
width: 25,
|
|
height: 25,
|
|
child: Platform.isIOS
|
|
? const CupertinoActivityIndicator()
|
|
: const CircularProgressIndicator(
|
|
strokeWidth: 2.0,
|
|
valueColor:
|
|
AlwaysStoppedAnimation(AppColors.actionRed),
|
|
),
|
|
),
|
|
textStyle: TextStyle(
|
|
color: AppColors.tipTextColor99,
|
|
fontSize: 12,
|
|
decoration: TextDecoration.none,
|
|
fontWeight: FontWeight.normal,
|
|
),
|
|
),
|
|
child: widget.child),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
//还原 Flutter 3.16 时代的滚动行为
|
|
class NoStretchScrollBehavior extends MaterialScrollBehavior {
|
|
//去掉 StretchingOverscrollIndicator,避免与下拉刷新冲突
|
|
@override
|
|
Widget buildOverscrollIndicator(
|
|
BuildContext context,
|
|
Widget child,
|
|
ScrollableDetails details,
|
|
) {
|
|
return child;
|
|
}
|
|
|
|
//还原旧版多指拖拽策略(3.19+ 改为 latestPointer,拖拽手感有细微差异)
|
|
@override
|
|
MultitouchDragStrategy getMultitouchDragStrategy(BuildContext context) {
|
|
return MultitouchDragStrategy.averageBoundaryPointers;
|
|
}
|
|
|
|
//使用 BouncingScrollPhysics 还原弹性下拉手感
|
|
@override
|
|
ScrollPhysics getScrollPhysics(BuildContext context) {
|
|
return const BouncingScrollPhysics();
|
|
}
|
|
}
|