classic_header.dart 17 KB

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