refresh_header.dart 16 KB

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