refresh_footer.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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,
  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(BuildContext context, LoadMode loadState, double pulledExtent, double loadTriggerPullDistance, double loadIndicatorExtent,
  68. AxisDirection axisDirection, bool float, Duration completeDuration, bool enableInfiniteLoad, bool success, bool noMore) {
  69. return ClassicalFooterWidget(
  70. key: key,
  71. classicalFooter: this,
  72. loadState: loadState,
  73. pulledExtent: pulledExtent,
  74. loadTriggerPullDistance: loadTriggerPullDistance,
  75. loadIndicatorExtent: loadIndicatorExtent,
  76. axisDirection: axisDirection,
  77. float: float,
  78. completeDuration: completeDuration,
  79. enableInfiniteLoad: enableInfiniteLoad,
  80. success: success,
  81. noMore: noMore,
  82. );
  83. }
  84. }
  85. /// 经典Footer组件
  86. class ClassicalFooterWidget extends StatefulWidget {
  87. final CustomClassicalFooter classicalFooter;
  88. final LoadMode loadState;
  89. final double pulledExtent;
  90. final double loadTriggerPullDistance;
  91. final double loadIndicatorExtent;
  92. final AxisDirection axisDirection;
  93. final bool float;
  94. final Duration completeDuration;
  95. final bool enableInfiniteLoad;
  96. final bool success;
  97. final bool noMore;
  98. ClassicalFooterWidget(
  99. {Key key,
  100. this.loadState,
  101. this.classicalFooter,
  102. this.pulledExtent,
  103. this.loadTriggerPullDistance,
  104. this.loadIndicatorExtent,
  105. this.axisDirection,
  106. this.float,
  107. this.completeDuration,
  108. this.enableInfiniteLoad,
  109. this.success,
  110. this.noMore})
  111. : super(key: key);
  112. @override
  113. ClassicalFooterWidgetState createState() => ClassicalFooterWidgetState();
  114. }
  115. class ClassicalFooterWidgetState extends State<ClassicalFooterWidget> with TickerProviderStateMixin<ClassicalFooterWidget> {
  116. // 是否到达触发加载距离
  117. bool _overTriggerDistance = false;
  118. bool get overTriggerDistance => _overTriggerDistance;
  119. set overTriggerDistance(bool over) {
  120. if (_overTriggerDistance != over) {
  121. _overTriggerDistance ? _readyController.forward() : _restoreController.forward();
  122. }
  123. _overTriggerDistance = over;
  124. }
  125. /// 文本
  126. String get _loadText {
  127. return widget.classicalFooter.loadText ?? 'Push to load';
  128. }
  129. String get _loadReadyText {
  130. return widget.classicalFooter.loadReadyText ?? 'Release to load';
  131. }
  132. String get _loadingText {
  133. return widget.classicalFooter.loadingText ?? 'Loading...';
  134. }
  135. String get _loadedText {
  136. return widget.classicalFooter.loadedText ?? 'Load completed';
  137. }
  138. String get _loadFailedText {
  139. return widget.classicalFooter.loadFailedText ?? 'Load failed';
  140. }
  141. /// 没有更多文字
  142. String get _noMoreText {
  143. return widget.classicalFooter.noMoreText ?? 'No more';
  144. }
  145. String get _infoText {
  146. return widget.classicalFooter.infoText ?? 'Update at %T';
  147. }
  148. // 动画
  149. AnimationController _readyController;
  150. Animation<double> _readyAnimation;
  151. AnimationController _restoreController;
  152. Animation<double> _restoreAnimation;
  153. // Icon旋转度
  154. double _iconRotationValue = 1.0;
  155. // 显示文字
  156. String get _showText {
  157. if (widget.noMore) return _noMoreText;
  158. if (widget.enableInfiniteLoad) {
  159. if (widget.loadState == LoadMode.loaded || widget.loadState == LoadMode.inactive || widget.loadState == LoadMode.drag) {
  160. return _finishedText;
  161. } else {
  162. return _loadingText;
  163. }
  164. }
  165. switch (widget.loadState) {
  166. case LoadMode.load:
  167. return _loadingText;
  168. case LoadMode.armed:
  169. return _loadingText;
  170. case LoadMode.loaded:
  171. return _finishedText;
  172. case LoadMode.done:
  173. return _finishedText;
  174. default:
  175. if (overTriggerDistance) {
  176. return _loadReadyText;
  177. } else {
  178. return _loadText;
  179. }
  180. }
  181. }
  182. // 加载结束文字
  183. String get _finishedText {
  184. if (!widget.success) return _loadFailedText;
  185. if (widget.noMore) return _noMoreText;
  186. return _loadedText;
  187. }
  188. // 加载结束图标
  189. IconData get _finishedIcon {
  190. if (!widget.success) return Icons.error_outline;
  191. if (widget.noMore) return Icons.hourglass_empty;
  192. return Icons.done;
  193. }
  194. // 更新时间
  195. DateTime _dateTime;
  196. // 获取更多信息
  197. String get _infoTextStr {
  198. if (widget.loadState == LoadMode.loaded) {
  199. _dateTime = DateTime.now();
  200. }
  201. String fillChar = _dateTime.minute < 10 ? "0" : "";
  202. return _infoText.replaceAll("%T", "${_dateTime.hour}:$fillChar${_dateTime.minute}");
  203. }
  204. @override
  205. void initState() {
  206. super.initState();
  207. // 初始化时间
  208. _dateTime = DateTime.now();
  209. // 初始化动画
  210. _readyController = new AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
  211. _readyAnimation = new Tween(begin: 0.5, end: 1.0).animate(_readyController)
  212. ..addListener(() {
  213. setState(() {
  214. if (_readyAnimation.status != AnimationStatus.dismissed) {
  215. _iconRotationValue = _readyAnimation.value;
  216. }
  217. });
  218. });
  219. _readyAnimation.addStatusListener((status) {
  220. if (status == AnimationStatus.completed) {
  221. _readyController.reset();
  222. }
  223. });
  224. _restoreController = new AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
  225. _restoreAnimation = new Tween(begin: 1.0, end: 0.5).animate(_restoreController)
  226. ..addListener(() {
  227. setState(() {
  228. if (_restoreAnimation.status != AnimationStatus.dismissed) {
  229. _iconRotationValue = _restoreAnimation.value;
  230. }
  231. });
  232. });
  233. _restoreAnimation.addStatusListener((status) {
  234. if (status == AnimationStatus.completed) {
  235. _restoreController.reset();
  236. }
  237. });
  238. }
  239. @override
  240. void dispose() {
  241. _readyController.dispose();
  242. _restoreController.dispose();
  243. super.dispose();
  244. }
  245. @override
  246. Widget build(BuildContext context) {
  247. // 是否为垂直方向
  248. bool isVertical = widget.axisDirection == AxisDirection.down || widget.axisDirection == AxisDirection.up;
  249. // 是否反向
  250. bool isReverse = widget.axisDirection == AxisDirection.up || widget.axisDirection == AxisDirection.left;
  251. // 是否到达触发加载距离
  252. overTriggerDistance = widget.loadState != LoadMode.inactive && widget.pulledExtent >= widget.loadTriggerPullDistance;
  253. return Stack(
  254. children: <Widget>[
  255. Positioned(
  256. top: !isVertical ? 0.0 : !isReverse ? 0.0 : null,
  257. bottom: !isVertical ? 0.0 : isReverse ? 0.0 : null,
  258. left: isVertical ? 0.0 : !isReverse ? 0.0 : null,
  259. right: isVertical ? 0.0 : isReverse ? 0.0 : null,
  260. child: Container(
  261. alignment: widget.classicalFooter.alignment ?? isVertical
  262. ? !isReverse ? Alignment.topCenter : Alignment.bottomCenter
  263. : isReverse ? Alignment.centerRight : Alignment.centerLeft,
  264. width: !isVertical ? widget.loadIndicatorExtent > widget.pulledExtent ? widget.loadIndicatorExtent : widget.pulledExtent : double.infinity,
  265. height: isVertical ? widget.loadIndicatorExtent > widget.pulledExtent ? widget.loadIndicatorExtent : widget.pulledExtent : double.infinity,
  266. color: widget.classicalFooter.bgColor,
  267. child: SizedBox(
  268. height: isVertical ? widget.loadIndicatorExtent : double.infinity,
  269. width: !isVertical ? widget.loadIndicatorExtent : double.infinity,
  270. child: isVertical
  271. ? Row(
  272. mainAxisAlignment: MainAxisAlignment.center,
  273. children: _buildContent(isVertical, isReverse),
  274. )
  275. : Column(
  276. mainAxisAlignment: MainAxisAlignment.center,
  277. children: _buildContent(isVertical, isReverse),
  278. ),
  279. ),
  280. ),
  281. ),
  282. ],
  283. );
  284. }
  285. // 构建显示内容
  286. List<Widget> _buildContent(bool isVertical, bool isReverse) {
  287. return isVertical
  288. ? <Widget>[
  289. Expanded(
  290. flex: 2,
  291. child: Container(
  292. alignment: Alignment.centerRight,
  293. padding: EdgeInsets.only(
  294. right: 10.0,
  295. ),
  296. child: (widget.loadState == LoadMode.load || widget.loadState == LoadMode.armed) && !widget.noMore
  297. ? Container(
  298. width: 20.0,
  299. height: 20.0,
  300. child: CircularProgressIndicator(
  301. strokeWidth: 2.0,
  302. valueColor: AlwaysStoppedAnimation(
  303. widget.classicalFooter.textColor,
  304. ),
  305. ),
  306. )
  307. : widget.loadState == LoadMode.loaded ||
  308. widget.loadState == LoadMode.done ||
  309. (widget.enableInfiniteLoad && widget.loadState != LoadMode.loaded) ||
  310. widget.noMore
  311. ? Container()
  312. : Transform.rotate(
  313. child: Icon(
  314. !isReverse ? Icons.arrow_upward : Icons.arrow_downward,
  315. color: widget.classicalFooter.textColor,
  316. ),
  317. angle: 2 * pi * _iconRotationValue,
  318. ),
  319. ),
  320. ),
  321. Expanded(
  322. flex: 3,
  323. child: Column(
  324. crossAxisAlignment: CrossAxisAlignment.center,
  325. mainAxisAlignment: MainAxisAlignment.center,
  326. children: <Widget>[
  327. Text(
  328. _showText,
  329. style: TextStyle(
  330. fontSize: 12.0,
  331. color: widget.classicalFooter.infoColor,
  332. ),
  333. ),
  334. widget.classicalFooter.showInfo
  335. ? Container(
  336. margin: EdgeInsets.only(
  337. top: 2.0,
  338. ),
  339. child: Text(
  340. _infoTextStr,
  341. style: TextStyle(
  342. fontSize: 12.0,
  343. color: widget.classicalFooter.infoColor,
  344. ),
  345. ),
  346. )
  347. : Container(),
  348. ],
  349. ),
  350. ),
  351. Expanded(
  352. flex: 2,
  353. child: SizedBox(),
  354. ),
  355. ]
  356. : <Widget>[
  357. Container(
  358. child: widget.loadState == LoadMode.load || widget.loadState == LoadMode.armed
  359. ? Container(
  360. width: 20.0,
  361. height: 20.0,
  362. child: CircularProgressIndicator(
  363. strokeWidth: 2.0,
  364. valueColor: AlwaysStoppedAnimation(
  365. widget.classicalFooter.textColor,
  366. ),
  367. ),
  368. )
  369. : widget.loadState == LoadMode.loaded ||
  370. widget.loadState == LoadMode.done ||
  371. (widget.enableInfiniteLoad && widget.loadState != LoadMode.loaded) ||
  372. widget.noMore
  373. ? Icon(
  374. _finishedIcon,
  375. color: widget.classicalFooter.textColor,
  376. )
  377. : Transform.rotate(
  378. child: Icon(
  379. !isReverse ? Icons.arrow_back : Icons.arrow_forward,
  380. color: widget.classicalFooter.textColor,
  381. ),
  382. angle: 2 * pi * _iconRotationValue,
  383. ),
  384. ),
  385. ];
  386. }
  387. }