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 createState() => _CustomIRefreshViewState(); } class _CustomIRefreshViewState extends State { 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(); } }