refresh_footer.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter_easyrefresh/easy_refresh.dart';
  4. /// 经典Footer
  5. class CustomClassicalFooter extends Footer {
  6. /// Key
  7. final Key? key;
  8. /// 方位
  9. final AlignmentGeometry? alignment;
  10. /// 提示加载文字
  11. final String? loadText;
  12. /// 准备加载文字
  13. final String? loadReadyText;
  14. /// 正在加载文字
  15. final String? loadingText;
  16. /// 加载完成文字
  17. final String? loadedText;
  18. /// 加载失败文字
  19. final String? loadFailedText;
  20. /// 没有更多文字
  21. final String? noMoreText;
  22. /// 显示额外信息(默认为时间)
  23. final bool? showInfo;
  24. /// 更多信息
  25. final String? infoText;
  26. /// 背景颜色
  27. final Color? bgColor;
  28. /// 字体颜色
  29. final Color? textColor;
  30. /// 更多信息文字颜色
  31. final Color? infoColor;
  32. CustomClassicalFooter({
  33. double extent = 60.0,
  34. double triggerDistance = 70.0,
  35. bool float = false,
  36. Duration completeDuration = const Duration(seconds: 1),
  37. bool enableInfiniteLoad = true,
  38. bool enableHapticFeedback = true,
  39. bool overScroll = false,
  40. bool safeArea = true,
  41. EdgeInsets padding = EdgeInsets.zero,
  42. this.key,
  43. this.alignment,
  44. this.loadText,
  45. this.loadReadyText,
  46. this.loadingText,
  47. this.loadedText,
  48. this.loadFailedText,
  49. this.noMoreText,
  50. this.showInfo: true,
  51. this.infoText,
  52. this.bgColor: Colors.transparent,
  53. this.textColor: Colors.black,
  54. this.infoColor: Colors.teal,
  55. }) : super(
  56. extent: extent,
  57. triggerDistance: triggerDistance,
  58. float: float,
  59. completeDuration: completeDuration,
  60. enableInfiniteLoad: enableInfiniteLoad,
  61. enableHapticFeedback: enableHapticFeedback,
  62. overScroll: overScroll,
  63. safeArea: safeArea,
  64. padding: padding,
  65. );
  66. @override
  67. Widget contentBuilder(
  68. BuildContext context,
  69. LoadMode loadState,
  70. double pulledExtent,
  71. double loadTriggerPullDistance,
  72. double loadIndicatorExtent,
  73. AxisDirection axisDirection,
  74. bool float,
  75. Duration? completeDuration,
  76. bool enableInfiniteLoad,
  77. bool success,
  78. bool noMore) {
  79. return ClassicalFooterWidget(
  80. key: key,
  81. classicalFooter: this,
  82. loadState: loadState,
  83. pulledExtent: pulledExtent,
  84. loadTriggerPullDistance: loadTriggerPullDistance,
  85. loadIndicatorExtent: loadIndicatorExtent,
  86. axisDirection: axisDirection,
  87. float: float,
  88. completeDuration: completeDuration,
  89. enableInfiniteLoad: enableInfiniteLoad,
  90. success: success,
  91. noMore: noMore,
  92. );
  93. }
  94. }
  95. /// 经典Footer组件
  96. class ClassicalFooterWidget extends StatefulWidget {
  97. final CustomClassicalFooter classicalFooter;
  98. final LoadMode loadState;
  99. final double pulledExtent;
  100. final double loadTriggerPullDistance;
  101. final double loadIndicatorExtent;
  102. final AxisDirection axisDirection;
  103. final bool float;
  104. final Duration? completeDuration;
  105. final bool enableInfiniteLoad;
  106. final bool success;
  107. final bool noMore;
  108. ClassicalFooterWidget(
  109. {Key? key,
  110. required this.loadState,
  111. required this.classicalFooter,
  112. required this.pulledExtent,
  113. required this.loadTriggerPullDistance,
  114. required this.loadIndicatorExtent,
  115. required this.axisDirection,
  116. required this.float,
  117. required this.completeDuration,
  118. required this.enableInfiniteLoad,
  119. required this.success,
  120. required this.noMore})
  121. : super(key: key);
  122. @override
  123. ClassicalFooterWidgetState createState() => ClassicalFooterWidgetState();
  124. }
  125. class ClassicalFooterWidgetState extends State<ClassicalFooterWidget>
  126. with TickerProviderStateMixin<ClassicalFooterWidget> {
  127. // 是否到达触发加载距离
  128. bool _overTriggerDistance = false;
  129. bool get overTriggerDistance => _overTriggerDistance;
  130. set overTriggerDistance(bool over) {
  131. if (_overTriggerDistance != over) {
  132. _overTriggerDistance
  133. ? _readyController.forward()
  134. : _restoreController.forward();
  135. }
  136. _overTriggerDistance = over;
  137. }
  138. /// 文本
  139. String get _loadText {
  140. return widget.classicalFooter.loadText ?? 'Push to load';
  141. }
  142. String get _loadReadyText {
  143. return widget.classicalFooter.loadReadyText ?? 'Release to load';
  144. }
  145. String get _loadingText {
  146. return widget.classicalFooter.loadingText ?? 'Loading...';
  147. }
  148. String get _loadedText {
  149. return widget.classicalFooter.loadedText ?? 'Load completed';
  150. }
  151. String get _loadFailedText {
  152. return widget.classicalFooter.loadFailedText ?? 'Load failed';
  153. }
  154. /// 没有更多文字
  155. String get _noMoreText {
  156. return widget.classicalFooter.noMoreText ?? 'No more';
  157. }
  158. String get _infoText {
  159. return widget.classicalFooter.infoText ?? 'Update at %T';
  160. }
  161. // 动画
  162. late AnimationController _readyController;
  163. late Animation<double> _readyAnimation;
  164. late AnimationController _restoreController;
  165. late Animation<double> _restoreAnimation;
  166. // Icon旋转度
  167. double _iconRotationValue = 1.0;
  168. // 显示文字
  169. String get _showText {
  170. if (widget.noMore) return _noMoreText;
  171. if (widget.enableInfiniteLoad) {
  172. if (widget.loadState == LoadMode.loaded ||
  173. widget.loadState == LoadMode.inactive ||
  174. widget.loadState == LoadMode.drag) {
  175. return _finishedText;
  176. } else {
  177. return _loadingText;
  178. }
  179. }
  180. switch (widget.loadState) {
  181. case LoadMode.load:
  182. return _loadingText;
  183. case LoadMode.armed:
  184. return _loadingText;
  185. case LoadMode.loaded:
  186. return _finishedText;
  187. case LoadMode.done:
  188. return _finishedText;
  189. default:
  190. if (overTriggerDistance) {
  191. return _loadReadyText;
  192. } else {
  193. return _loadText;
  194. }
  195. }
  196. }
  197. // 加载结束文字
  198. String get _finishedText {
  199. if (!widget.success) return _loadFailedText;
  200. if (widget.noMore) return _noMoreText;
  201. return _loadedText;
  202. }
  203. // 加载结束图标
  204. IconData get _finishedIcon {
  205. if (!widget.success) return Icons.error_outline;
  206. if (widget.noMore) return Icons.hourglass_empty;
  207. return Icons.done;
  208. }
  209. // 更新时间
  210. late DateTime _dateTime;
  211. // 获取更多信息
  212. String get _infoTextStr {
  213. if (widget.loadState == LoadMode.loaded) {
  214. _dateTime = DateTime.now();
  215. }
  216. String fillChar = _dateTime.minute < 10 ? "0" : "";
  217. return _infoText.replaceAll(
  218. "%T", "${_dateTime.hour}:$fillChar${_dateTime.minute}");
  219. }
  220. @override
  221. void initState() {
  222. super.initState();
  223. // 初始化时间
  224. _dateTime = DateTime.now();
  225. // 初始化动画
  226. _readyController = new AnimationController(
  227. duration: const Duration(milliseconds: 200), vsync: this);
  228. _readyAnimation = new Tween(begin: 0.5, end: 1.0).animate(_readyController)
  229. ..addListener(() {
  230. setState(() {
  231. if (_readyAnimation.status != AnimationStatus.dismissed) {
  232. _iconRotationValue = _readyAnimation.value;
  233. }
  234. });
  235. });
  236. _readyAnimation.addStatusListener((status) {
  237. if (status == AnimationStatus.completed) {
  238. _readyController.reset();
  239. }
  240. });
  241. _restoreController = new AnimationController(
  242. duration: const Duration(milliseconds: 200), vsync: this);
  243. _restoreAnimation =
  244. new Tween(begin: 1.0, end: 0.5).animate(_restoreController)
  245. ..addListener(() {
  246. setState(() {
  247. if (_restoreAnimation.status != AnimationStatus.dismissed) {
  248. _iconRotationValue = _restoreAnimation.value;
  249. }
  250. });
  251. });
  252. _restoreAnimation.addStatusListener((status) {
  253. if (status == AnimationStatus.completed) {
  254. _restoreController.reset();
  255. }
  256. });
  257. }
  258. @override
  259. void dispose() {
  260. _readyController.dispose();
  261. _restoreController.dispose();
  262. super.dispose();
  263. }
  264. @override
  265. Widget build(BuildContext context) {
  266. // 是否为垂直方向
  267. bool isVertical = widget.axisDirection == AxisDirection.down ||
  268. widget.axisDirection == AxisDirection.up;
  269. // 是否反向
  270. bool isReverse = widget.axisDirection == AxisDirection.up ||
  271. widget.axisDirection == AxisDirection.left;
  272. // 是否到达触发加载距离
  273. overTriggerDistance = widget.loadState != LoadMode.inactive &&
  274. widget.pulledExtent >= widget.loadTriggerPullDistance;
  275. return Stack(
  276. children: <Widget>[
  277. Positioned(
  278. top: !isVertical ? 0.0 : !isReverse ? 0.0 : null,
  279. bottom: !isVertical ? 0.0 : isReverse ? 0.0 : null,
  280. left: isVertical ? 0.0 : !isReverse ? 0.0 : null,
  281. right: isVertical ? 0.0 : isReverse ? 0.0 : null,
  282. child: Container(
  283. alignment: widget.classicalFooter.alignment ?? (isVertical
  284. ? !isReverse ? Alignment.topCenter : Alignment.bottomCenter
  285. : isReverse ? Alignment.centerRight : Alignment.centerLeft),
  286. width: !isVertical
  287. ? widget.loadIndicatorExtent > widget.pulledExtent
  288. ? widget.loadIndicatorExtent
  289. : widget.pulledExtent
  290. : double.infinity,
  291. height: isVertical
  292. ? widget.loadIndicatorExtent > widget.pulledExtent
  293. ? widget.loadIndicatorExtent
  294. : widget.pulledExtent
  295. : double.infinity,
  296. color: widget.classicalFooter.bgColor,
  297. child: SizedBox(
  298. height: isVertical ? widget.loadIndicatorExtent : double.infinity,
  299. width: !isVertical ? widget.loadIndicatorExtent : double.infinity,
  300. child: isVertical
  301. ? Row(
  302. mainAxisAlignment: MainAxisAlignment.center,
  303. children: _buildContent(isVertical, isReverse),
  304. )
  305. : Column(
  306. mainAxisAlignment: MainAxisAlignment.center,
  307. children: _buildContent(isVertical, isReverse),
  308. ),
  309. ),
  310. ),
  311. ),
  312. ],
  313. );
  314. }
  315. // 构建显示内容
  316. List<Widget> _buildContent(bool isVertical, bool isReverse) {
  317. return isVertical
  318. ? <Widget>[
  319. Expanded(
  320. flex: 2,
  321. child: Container(
  322. alignment: Alignment.centerRight,
  323. padding: EdgeInsets.only(
  324. right: 10.0,
  325. ),
  326. child: (widget.loadState == LoadMode.load ||
  327. widget.loadState == LoadMode.armed) &&
  328. !widget.noMore
  329. ? Container(
  330. width: 20.0,
  331. height: 20.0,
  332. child: CircularProgressIndicator(
  333. strokeWidth: 2.0,
  334. valueColor: AlwaysStoppedAnimation(
  335. widget.classicalFooter.textColor,
  336. ),
  337. ),
  338. )
  339. : widget.loadState == LoadMode.loaded ||
  340. widget.loadState == LoadMode.done ||
  341. (widget.enableInfiniteLoad &&
  342. widget.loadState != LoadMode.loaded) ||
  343. widget.noMore
  344. ? Container()
  345. : Transform.rotate(
  346. child: Icon(
  347. !isReverse
  348. ? Icons.arrow_upward
  349. : Icons.arrow_downward,
  350. color: widget.classicalFooter.textColor,
  351. ),
  352. angle: 2 * pi * _iconRotationValue,
  353. ),
  354. ),
  355. ),
  356. Expanded(
  357. flex: 3,
  358. child: Column(
  359. crossAxisAlignment: CrossAxisAlignment.center,
  360. mainAxisAlignment: MainAxisAlignment.center,
  361. children: <Widget>[
  362. Text(
  363. _showText,
  364. style: TextStyle(
  365. fontSize: 12.0,
  366. color: widget.classicalFooter.infoColor,
  367. ),
  368. ),
  369. widget.classicalFooter.showInfo == true
  370. ? Container(
  371. margin: EdgeInsets.only(
  372. top: 2.0,
  373. ),
  374. child: Text(
  375. _infoTextStr,
  376. style: TextStyle(
  377. fontSize: 12.0,
  378. color: widget.classicalFooter.infoColor,
  379. ),
  380. ),
  381. )
  382. : Container(),
  383. ],
  384. ),
  385. ),
  386. Expanded(
  387. flex: 2,
  388. child: SizedBox(),
  389. ),
  390. ]
  391. : <Widget>[
  392. Container(
  393. child: widget.loadState == LoadMode.load ||
  394. widget.loadState == LoadMode.armed
  395. ? Container(
  396. width: 20.0,
  397. height: 20.0,
  398. child: CircularProgressIndicator(
  399. strokeWidth: 2.0,
  400. valueColor: AlwaysStoppedAnimation(
  401. widget.classicalFooter.textColor,
  402. ),
  403. ),
  404. )
  405. : widget.loadState == LoadMode.loaded ||
  406. widget.loadState == LoadMode.done ||
  407. (widget.enableInfiniteLoad &&
  408. widget.loadState != LoadMode.loaded) ||
  409. widget.noMore
  410. ? Icon(
  411. _finishedIcon,
  412. color: widget.classicalFooter.textColor,
  413. )
  414. : Transform.rotate(
  415. child: Icon(
  416. !isReverse ? Icons.arrow_back : Icons.arrow_forward,
  417. color: widget.classicalFooter.textColor,
  418. ),
  419. angle: 2 * pi * _iconRotationValue,
  420. ),
  421. ),
  422. ];
  423. }
  424. }