refresh_header.dart 17 KB

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