classic_footer.dart 14 KB

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