refresh_header.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  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. // 是否到达触发刷新距离
  122. bool _overTriggerDistance = false;
  123. bool get overTriggerDistance => _overTriggerDistance;
  124. set overTriggerDistance(bool over) {
  125. if (_overTriggerDistance != over) {
  126. _overTriggerDistance ? _readyController.forward() : _restoreController.forward();
  127. _overTriggerDistance = over;
  128. }
  129. }
  130. /// 文本
  131. String get _refreshText {
  132. return widget.classicalHeader.refreshText ?? 'Pull to refresh';
  133. }
  134. String get _refreshReadyText {
  135. return widget.classicalHeader.refreshReadyText ?? 'Release to refresh';
  136. }
  137. String get _refreshingText {
  138. return widget.classicalHeader.refreshingText ?? 'Refreshing...';
  139. }
  140. String get _refreshedText {
  141. return widget.classicalHeader.refreshedText ?? 'Refresh completed';
  142. }
  143. String get _refreshFailedText {
  144. return widget.classicalHeader.refreshFailedText ?? 'Refresh failed';
  145. }
  146. String get _noMoreText {
  147. return widget.classicalHeader.noMoreText ?? 'No more';
  148. }
  149. String get _infoText {
  150. return widget.classicalHeader.infoText ?? 'Update at %T';
  151. }
  152. // 是否刷新完成
  153. bool _refreshFinish = false;
  154. set refreshFinish(bool finish) {
  155. if (_refreshFinish != finish) {
  156. if (finish && widget.float) {
  157. Future.delayed(widget.completeDuration - Duration(milliseconds: 400), () {
  158. if (mounted) {
  159. _floatBackController.forward();
  160. }
  161. });
  162. Future.delayed(widget.completeDuration, () {
  163. _floatBackDistance = null;
  164. _refreshFinish = false;
  165. });
  166. }
  167. _refreshFinish = finish;
  168. }
  169. }
  170. // 动画
  171. AnimationController _readyController;
  172. Animation<double> _readyAnimation;
  173. AnimationController _restoreController;
  174. Animation<double> _restoreAnimation;
  175. AnimationController _floatBackController;
  176. Animation<double> _floatBackAnimation;
  177. // Icon旋转度
  178. double _iconRotationValue = 1.0;
  179. // 浮动时,收起距离
  180. double _floatBackDistance;
  181. AnimationController _animationController;
  182. Animation _animation;
  183. // 显示文字
  184. String get _showText {
  185. if (widget.noMore) return _noMoreText;
  186. if (widget.enableInfiniteRefresh) {
  187. if (widget.refreshState == RefreshMode.refreshed || widget.refreshState == RefreshMode.inactive || widget.refreshState == RefreshMode.drag) {
  188. return _finishedText;
  189. } else {
  190. return _refreshingText;
  191. }
  192. }
  193. switch (widget.refreshState) {
  194. case RefreshMode.refresh:
  195. return _refreshingText;
  196. case RefreshMode.armed:
  197. return _refreshingText;
  198. case RefreshMode.refreshed:
  199. return _finishedText;
  200. case RefreshMode.done:
  201. return _finishedText;
  202. default:
  203. if (overTriggerDistance) {
  204. return _refreshReadyText;
  205. } else {
  206. return _refreshText;
  207. }
  208. }
  209. }
  210. // 刷新结束文字
  211. String get _finishedText {
  212. if (!widget.success) return _refreshFailedText;
  213. if (widget.noMore) return _noMoreText;
  214. return _refreshedText;
  215. }
  216. // 刷新结束图标
  217. IconData get _finishedIcon {
  218. if (!widget.success) return Icons.error_outline;
  219. if (widget.noMore) return Icons.hourglass_empty;
  220. return Icons.done;
  221. }
  222. // 更新时间
  223. DateTime _dateTime;
  224. // 获取更多信息
  225. String get _infoTextStr {
  226. if (widget.refreshState == RefreshMode.refreshed) {
  227. _dateTime = DateTime.now();
  228. }
  229. String fillChar = _dateTime.minute < 10 ? "0" : "";
  230. return _infoText.replaceAll("%T", "${_dateTime.hour}:$fillChar${_dateTime.minute}");
  231. }
  232. @override
  233. void initState() {
  234. super.initState();
  235. // 初始化时间
  236. _dateTime = DateTime.now();
  237. // 准备动画
  238. _readyController = new AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
  239. _readyAnimation = new Tween(begin: 0.5, end: 1.0).animate(_readyController)
  240. ..addListener(() {
  241. setState(() {
  242. if (_readyAnimation.status != AnimationStatus.dismissed) {
  243. _iconRotationValue = _readyAnimation.value;
  244. }
  245. });
  246. });
  247. _readyAnimation.addStatusListener((status) {
  248. if (status == AnimationStatus.completed) {
  249. _readyController.reset();
  250. }
  251. });
  252. // 恢复动画
  253. _restoreController = new AnimationController(duration: const Duration(milliseconds: 200), vsync: this);
  254. _restoreAnimation = new Tween(begin: 1.0, end: 0.5).animate(_restoreController)
  255. ..addListener(() {
  256. setState(() {
  257. if (_restoreAnimation.status != AnimationStatus.dismissed) {
  258. _iconRotationValue = _restoreAnimation.value;
  259. }
  260. });
  261. });
  262. _restoreAnimation.addStatusListener((status) {
  263. if (status == AnimationStatus.completed) {
  264. _restoreController.reset();
  265. }
  266. });
  267. // float收起动画
  268. _floatBackController = new AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
  269. _floatBackAnimation = new Tween(begin: widget.refreshIndicatorExtent, end: 0.0).animate(_floatBackController)
  270. ..addListener(() {
  271. setState(() {
  272. if (_floatBackAnimation.status != AnimationStatus.dismissed) {
  273. _floatBackDistance = _floatBackAnimation.value;
  274. }
  275. });
  276. });
  277. _floatBackAnimation.addStatusListener((status) {
  278. if (status == AnimationStatus.completed) {
  279. _floatBackController.reset();
  280. }
  281. });
  282. _animationController = AnimationController(duration: Duration(seconds: 1), vsync: this);
  283. _animation = Tween(begin: .0, end: 1.0).animate(_animationController)
  284. ..addStatusListener((status) {
  285. if (status == AnimationStatus.completed) {
  286. _animationController.repeat();
  287. }
  288. });
  289. //开始动画
  290. _animationController.forward();
  291. }
  292. @override
  293. void dispose() {
  294. _readyController.dispose();
  295. _restoreController.dispose();
  296. _floatBackController.dispose();
  297. _animationController?.dispose();
  298. super.dispose();
  299. }
  300. @override
  301. Widget build(BuildContext context) {
  302. // 是否为垂直方向
  303. bool isVertical = widget.axisDirection == AxisDirection.down || widget.axisDirection == AxisDirection.up;
  304. // 是否反向
  305. bool isReverse = widget.axisDirection == AxisDirection.up || widget.axisDirection == AxisDirection.left;
  306. // 是否到达触发刷新距离
  307. overTriggerDistance = widget.refreshState != RefreshMode.inactive && widget.pulledExtent >= widget.refreshTriggerPullDistance;
  308. if (widget.refreshState == RefreshMode.refreshed) {
  309. refreshFinish = true;
  310. }
  311. return Stack(
  312. children: <Widget>[
  313. Positioned(
  314. top: !isVertical ? 0.0 : isReverse ? _floatBackDistance == null ? 0.0 : (widget.refreshIndicatorExtent - _floatBackDistance) : null,
  315. bottom: !isVertical ? 0.0 : !isReverse ? _floatBackDistance == null ? 0.0 : (widget.refreshIndicatorExtent - _floatBackDistance) : null,
  316. left: isVertical ? 0.0 : isReverse ? _floatBackDistance == null ? 0.0 : (widget.refreshIndicatorExtent - _floatBackDistance) : null,
  317. right: isVertical ? 0.0 : !isReverse ? _floatBackDistance == null ? 0.0 : (widget.refreshIndicatorExtent - _floatBackDistance) : null,
  318. child: Container(
  319. alignment: widget.classicalHeader.alignment ?? isVertical
  320. ? isReverse ? Alignment.topCenter : Alignment.bottomCenter
  321. : !isReverse ? Alignment.centerRight : Alignment.centerLeft,
  322. width: isVertical
  323. ? double.infinity
  324. : _floatBackDistance == null
  325. ? (widget.refreshIndicatorExtent > widget.pulledExtent ? widget.refreshIndicatorExtent : widget.pulledExtent)
  326. : widget.refreshIndicatorExtent,
  327. height: isVertical
  328. ? _floatBackDistance == null
  329. ? (widget.refreshIndicatorExtent > widget.pulledExtent ? widget.refreshIndicatorExtent : widget.pulledExtent)
  330. : widget.refreshIndicatorExtent
  331. : double.infinity,
  332. color: widget.classicalHeader.bgColor,
  333. // color: Colors.red,
  334. child: SizedBox(
  335. // height: isVertical ? widget.refreshIndicatorExtent : double.infinity,
  336. // width: !isVertical ? widget.refreshIndicatorExtent : double.infinity,
  337. child: isVertical
  338. ? Row(
  339. mainAxisAlignment: MainAxisAlignment.center,
  340. children: _buildContent(isVertical, isReverse),
  341. )
  342. : Column(
  343. mainAxisAlignment: MainAxisAlignment.center,
  344. children: _buildContent(isVertical, isReverse),
  345. ),
  346. ),
  347. ),
  348. ),
  349. ],
  350. );
  351. }
  352. // 构建显示内容
  353. List<Widget> _buildContent(bool isVertical, bool isReverse) {
  354. return isVertical
  355. ? <Widget>[
  356. Padding(
  357. padding: const EdgeInsets.all(10.0),
  358. child: Column(
  359. mainAxisSize: MainAxisSize.min,
  360. children: <Widget>[
  361. (widget.refreshState == RefreshMode.refresh || widget.refreshState == RefreshMode.armed) && !widget.noMore
  362. ? RotationTransition(
  363. turns: _animation,
  364. child: _loading,
  365. )
  366. : widget.refreshState == RefreshMode.refreshed ||
  367. widget.refreshState == RefreshMode.done ||
  368. (widget.enableInfiniteRefresh && widget.refreshState != RefreshMode.refreshed) ||
  369. widget.noMore
  370. ? _loading
  371. : Transform.rotate(
  372. child: _loading,
  373. angle: 2 * pi * (widget.pulledExtent / widget.refreshTriggerPullDistance),
  374. ),
  375. SizedBox(height: 5,),
  376. Column(
  377. crossAxisAlignment: CrossAxisAlignment.center,
  378. mainAxisAlignment: MainAxisAlignment.center,
  379. children: <Widget>[
  380. Text(
  381. _showText,
  382. style: TextStyle(
  383. fontSize: 12.0,
  384. color: widget.classicalHeader.textColor,
  385. ),
  386. ),
  387. widget.classicalHeader.showInfo
  388. ? Container(
  389. margin: EdgeInsets.only(
  390. top: 2.0,
  391. ),
  392. child: Text(
  393. _infoTextStr,
  394. style: TextStyle(
  395. fontSize: 12.0,
  396. color: widget.classicalHeader.infoColor,
  397. ),
  398. ),
  399. )
  400. : Container(),
  401. ],
  402. )
  403. ],
  404. ),
  405. )
  406. ]
  407. : <Widget>[
  408. Container(
  409. child: widget.refreshState == RefreshMode.refresh || widget.refreshState == RefreshMode.armed
  410. ? Container(
  411. width: 20.0,
  412. height: 20.0,
  413. child: CircularProgressIndicator(
  414. strokeWidth: 2.0,
  415. valueColor: AlwaysStoppedAnimation(
  416. widget.classicalHeader.textColor,
  417. ),
  418. ),
  419. )
  420. : widget.refreshState == RefreshMode.refreshed ||
  421. widget.refreshState == RefreshMode.done ||
  422. (widget.enableInfiniteRefresh && widget.refreshState != RefreshMode.refreshed) ||
  423. widget.noMore
  424. ? Icon(
  425. _finishedIcon,
  426. color: widget.classicalHeader.textColor,
  427. )
  428. : Transform.rotate(
  429. child: Icon(
  430. isReverse ? Icons.arrow_back : Icons.arrow_forward,
  431. color: widget.classicalHeader.textColor,
  432. ),
  433. angle: 2 * pi * _iconRotationValue,
  434. ),
  435. )
  436. ];
  437. }
  438. }