pretty_refresher.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. import 'package:eye_video/framework/uikit/refresher/empty/empty_widget.dart';
  2. import 'package:eye_video/framework/uikit/refresher/indicator/core/abstract_footer.dart';
  3. import 'package:eye_video/framework/uikit/refresher/indicator/core/abstract_header.dart';
  4. import 'package:eye_video/framework/uikit/refresher/indicator/core/first_refresh_header.dart';
  5. import 'package:eye_video/framework/uikit/refresher/indicator/material/material_footer.dart';
  6. import 'package:eye_video/framework/uikit/refresher/indicator/material/material_header.dart';
  7. import 'package:eye_video/framework/uikit/refresher/listener/scroll_notification_listener.dart';
  8. import 'package:eye_video/framework/uikit/refresher/physics/scroll_physics.dart';
  9. import 'package:eye_video/framework/uikit/refresher/sliver/sliver_loading.dart';
  10. import 'package:eye_video/framework/uikit/refresher/sliver/sliver_refresh.dart';
  11. import 'package:flutter/gestures.dart';
  12. import 'package:flutter/scheduler.dart';
  13. import 'package:flutter/widgets.dart';
  14. // 子组件构造器
  15. typedef PrettyRefresherChildBuilder = Widget Function(
  16. BuildContext context, ScrollPhysics physics, Widget header, Widget footer);
  17. // 下拉刷新,上拉加载组件
  18. class PrettyRefresher extends StatefulWidget {
  19. // 控制器
  20. final RefreshController controller;
  21. // 刷新回调(null为不开启刷新)
  22. final OnRefreshCallback onRefresh;
  23. // 加载回调(null为不开启加载)
  24. final OnLoadCallback onLoad;
  25. // 是否开启控制结束刷新
  26. final bool enableControlFinishRefresh;
  27. // 是否开启控制结束加载
  28. final bool enableControlFinishLoad;
  29. // 任务独立(刷新和加载状态独立)
  30. final bool taskIndependence;
  31. // Header
  32. final Header header;
  33. final int headerIndex;
  34. // Footer
  35. final Footer footer;
  36. // 子组件构造器
  37. final PrettyRefresherChildBuilder builder;
  38. // 子组件
  39. final Widget child;
  40. // 首次刷新
  41. final bool firstRefresh;
  42. // 首次刷新组件
  43. // 不设置时使用header
  44. final Widget firstRefreshWidget;
  45. // 空视图
  46. // 当不为null时,只会显示空视图
  47. // 保留[headerIndex]以上的内容
  48. final Widget emptyWidget;
  49. // 顶部回弹(onRefresh和header都为null时生效)
  50. final bool topBouncing;
  51. // 底部回弹(onLoad和footer都为null时生效)
  52. final bool bottomBouncing;
  53. // CustomListView Key
  54. final Key listKey;
  55. // Slivers集合
  56. final List<Widget> slivers;
  57. // 列表方向
  58. final Axis scrollDirection;
  59. // 反向
  60. final bool reverse;
  61. final ScrollController scrollController;
  62. final bool primary;
  63. final bool shrinkWrap;
  64. final Key center;
  65. final double anchor;
  66. final double cacheExtent;
  67. final int semanticChildCount;
  68. final DragStartBehavior dragStartBehavior;
  69. // 全局默认Header
  70. static Header _defaultHeader = MaterialHeader();
  71. static set defaultHeader(Header header) {
  72. if (header != null) {
  73. _defaultHeader = header;
  74. }
  75. }
  76. // 全局默认Footer
  77. static Footer _defaultFooter = MaterialFooter();
  78. static set defaultFooter(Footer footer) {
  79. if (footer != null) {
  80. _defaultFooter = footer;
  81. }
  82. }
  83. // 触发时超过距离
  84. static double callOverExtent = 30.0;
  85. // 默认构造器
  86. // 将child转换为CustomScrollView可用的slivers
  87. PrettyRefresher({
  88. Key? key,
  89. this.controller,
  90. this.onRefresh,
  91. this.onLoad,
  92. this.enableControlFinishRefresh = false,
  93. this.enableControlFinishLoad = false,
  94. this.taskIndependence = false,
  95. this.scrollController,
  96. this.header,
  97. this.footer,
  98. this.firstRefresh,
  99. this.firstRefreshWidget,
  100. this.headerIndex,
  101. this.emptyWidget,
  102. this.topBouncing = true,
  103. this.bottomBouncing = true,
  104. required this.child,
  105. }) : this.scrollDirection = null,
  106. this.reverse = null,
  107. this.builder = null,
  108. this.primary = null,
  109. this.shrinkWrap = null,
  110. this.center = null,
  111. this.anchor = null,
  112. this.cacheExtent = null,
  113. this.slivers = null,
  114. this.semanticChildCount = null,
  115. this.dragStartBehavior = null,
  116. this.listKey = null,
  117. super(key: key);
  118. // custom构造器(推荐)
  119. // 直接使用CustomScrollView可用的slivers
  120. PrettyRefresher.custom({
  121. Key? key,
  122. this.listKey,
  123. this.controller,
  124. this.onRefresh,
  125. this.onLoad,
  126. this.enableControlFinishRefresh = false,
  127. this.enableControlFinishLoad = false,
  128. this.taskIndependence = false,
  129. this.header,
  130. this.headerIndex,
  131. this.footer,
  132. this.scrollDirection = Axis.vertical,
  133. this.reverse = false,
  134. this.scrollController,
  135. this.primary,
  136. this.shrinkWrap = false,
  137. this.center,
  138. this.anchor = 0.0,
  139. this.cacheExtent,
  140. this.semanticChildCount,
  141. this.dragStartBehavior = DragStartBehavior.start,
  142. this.firstRefresh,
  143. this.firstRefreshWidget,
  144. this.emptyWidget,
  145. this.topBouncing = true,
  146. this.bottomBouncing = true,
  147. required this.slivers,
  148. }) : this.builder = null,
  149. this.child = null,
  150. super(key: key);
  151. // 自定义构造器
  152. // 用法灵活,但需将physics、header和footer放入列表中
  153. PrettyRefresher.builder({
  154. Key? key,
  155. this.controller,
  156. this.onRefresh,
  157. this.onLoad,
  158. this.enableControlFinishRefresh = false,
  159. this.enableControlFinishLoad = false,
  160. this.taskIndependence = false,
  161. this.scrollController,
  162. this.header,
  163. this.footer,
  164. this.firstRefresh,
  165. this.topBouncing = true,
  166. this.bottomBouncing = true,
  167. required this.builder,
  168. }) : this.scrollDirection = null,
  169. this.reverse = null,
  170. this.child = null,
  171. this.primary = null,
  172. this.shrinkWrap = null,
  173. this.center = null,
  174. this.anchor = null,
  175. this.cacheExtent = null,
  176. this.slivers = null,
  177. this.semanticChildCount = null,
  178. this.dragStartBehavior = null,
  179. this.headerIndex = null,
  180. this.firstRefreshWidget = null,
  181. this.emptyWidget = null,
  182. this.listKey = null,
  183. super(key: key);
  184. @override
  185. _PrettyRefresherState createState() {
  186. return _PrettyRefresherState();
  187. }
  188. }
  189. class _PrettyRefresherState extends State<PrettyRefresher> {
  190. // Physics
  191. RefreshPhysics _physics;
  192. // Header
  193. Header get _header {
  194. if (_enableFirstRefresh && widget.firstRefreshWidget != null)
  195. return _firstRefreshHeader;
  196. return widget.header ?? PrettyRefresher._defaultHeader;
  197. }
  198. // 是否开启首次刷新
  199. bool _enableFirstRefresh = false;
  200. // 首次刷新组件
  201. Header _firstRefreshHeader;
  202. // Footer
  203. Footer get _footer => widget.footer ?? PrettyRefresher._defaultFooter;
  204. // 子组件的ScrollController
  205. ScrollController _childScrollController;
  206. // ScrollController
  207. ScrollController get _scrollerController {
  208. return widget.scrollController ??
  209. _childScrollController ??
  210. PrimaryScrollController.of(context);
  211. }
  212. // 滚动焦点状态
  213. ValueNotifier<bool> _focusNotifier;
  214. // 任务状态
  215. ValueNotifier<TaskState> _taskNotifier;
  216. // 触发刷新状态
  217. ValueNotifier<bool> _callRefreshNotifier;
  218. // 触发加载状态
  219. ValueNotifier<bool> _callLoadNotifier;
  220. // 回弹设置
  221. ValueNotifier<BouncingSettings> _bouncingNotifier;
  222. // 指示器越界
  223. ValueNotifier<RefreshIndicator> _indicatorNotifier;
  224. // 初始化
  225. @override
  226. void initState() {
  227. super.initState();
  228. _focusNotifier = ValueNotifier<bool>(false);
  229. _taskNotifier = ValueNotifier(TaskState());
  230. _callRefreshNotifier = ValueNotifier<bool>(false);
  231. _callLoadNotifier = ValueNotifier<bool>(false);
  232. _bouncingNotifier = ValueNotifier<BouncingSettings>(BouncingSettings());
  233. _indicatorNotifier = ValueNotifier<RefreshIndicator>(RefreshIndicator());
  234. _taskNotifier.addListener(() {
  235. // 监听首次刷新是否结束
  236. if (_enableFirstRefresh && !_taskNotifier.value.refreshing) {
  237. _scrollerController.jumpTo(0.0);
  238. setState(() {
  239. _enableFirstRefresh = false;
  240. });
  241. }
  242. });
  243. // 判断是否开启首次刷新
  244. _enableFirstRefresh = widget.firstRefresh ?? false;
  245. if (_enableFirstRefresh) {
  246. _firstRefreshHeader = FirstRefreshHeader(widget.firstRefreshWidget);
  247. SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
  248. callRefresh();
  249. });
  250. }
  251. _bindController();
  252. _createPhysics();
  253. }
  254. @override
  255. void didUpdateWidget(PrettyRefresher oldWidget) {
  256. super.didUpdateWidget(oldWidget);
  257. if (oldWidget.controller != widget.controller) {
  258. _bindController();
  259. }
  260. if (oldWidget.onRefresh != widget.onRefresh ||
  261. oldWidget.onLoad != widget.onLoad ||
  262. oldWidget.topBouncing != widget.topBouncing ||
  263. oldWidget.bottomBouncing != widget.bottomBouncing ||
  264. oldWidget.header != widget.header ||
  265. oldWidget.footer != widget.footer) {
  266. _createPhysics();
  267. }
  268. }
  269. // 销毁
  270. void dispose() {
  271. _focusNotifier.dispose();
  272. _taskNotifier.dispose();
  273. _callRefreshNotifier.dispose();
  274. _callLoadNotifier.dispose();
  275. _bouncingNotifier.dispose();
  276. _indicatorNotifier.dispose();
  277. super.dispose();
  278. }
  279. // 绑定Controller
  280. void _bindController() {
  281. // 绑定控制器
  282. if (widget.controller != null) widget.controller._bindRefreshState(this);
  283. }
  284. // 生成滚动物理形式
  285. void _createPhysics() {
  286. _bouncingNotifier.value = BouncingSettings(
  287. top: widget.onRefresh == null
  288. ? widget.header == null
  289. ? widget.topBouncing
  290. : widget.header.overScroll
  291. : true,
  292. bottom: widget.onLoad == null
  293. ? widget.footer == null
  294. ? widget.bottomBouncing
  295. : widget.footer.overScroll
  296. : true,
  297. );
  298. _indicatorNotifier.value = RefreshIndicator(
  299. header: widget.header == null ? null : _header,
  300. footer: widget.footer == null ? null : _footer,
  301. );
  302. _physics = RefreshPhysics(
  303. taskNotifier: _taskNotifier,
  304. bouncingNotifier: _bouncingNotifier,
  305. indicatorNotifier: _indicatorNotifier,
  306. );
  307. }
  308. // 触发刷新
  309. void callRefresh({Duration duration = const Duration(milliseconds: 300)}) {
  310. // ignore: invalid_use_of_protected_member
  311. if (_scrollerController == null || _scrollerController.positions.isEmpty)
  312. return;
  313. _callRefreshNotifier.value = true;
  314. _scrollerController
  315. .animateTo(0.0, duration: duration, curve: Curves.linear)
  316. .whenComplete(() {
  317. _scrollerController.animateTo(
  318. -(_header.triggerDistance + PrettyRefresher.callOverExtent),
  319. duration: Duration(milliseconds: 100),
  320. curve: Curves.linear);
  321. });
  322. }
  323. // 触发加载
  324. void callLoad({Duration duration = const Duration(milliseconds: 300)}) {
  325. // ignore: invalid_use_of_protected_member
  326. if (_scrollerController == null || _scrollerController.positions.isEmpty)
  327. return;
  328. _callLoadNotifier.value = true;
  329. _scrollerController
  330. .animateTo(_scrollerController.position.maxScrollExtent,
  331. duration: duration, curve: Curves.linear)
  332. .whenComplete(() {
  333. _scrollerController.animateTo(
  334. _scrollerController.position.maxScrollExtent +
  335. _footer.triggerDistance +
  336. PrettyRefresher.callOverExtent,
  337. duration: Duration(milliseconds: 100),
  338. curve: Curves.linear);
  339. });
  340. }
  341. @override
  342. Widget build(BuildContext context) {
  343. // 构建Header和Footer
  344. var header = widget.onRefresh == null
  345. ? null
  346. : _header.builder(context, widget, _focusNotifier, _taskNotifier,
  347. _callRefreshNotifier);
  348. var footer = widget.onLoad == null
  349. ? null
  350. : _footer.builder(
  351. context, widget, _focusNotifier, _taskNotifier, _callLoadNotifier);
  352. // 生成slivers
  353. List<Widget> slivers;
  354. if (widget.builder == null) {
  355. if (widget.slivers != null)
  356. slivers = List.from(
  357. widget.slivers,
  358. growable: true,
  359. );
  360. else if (widget.child != null) slivers = _buildSliversByChild();
  361. // 判断是否有空视图(自定义首次刷新视图不用添加,避免视图层叠)
  362. if (widget.emptyWidget != null &&
  363. slivers != null &&
  364. !(_enableFirstRefresh && widget.firstRefreshWidget != null)) {
  365. slivers = slivers.sublist(0, widget.headerIndex ?? 0);
  366. // 添加空元素避免异常
  367. slivers.add(SliverList(
  368. delegate: SliverChildListDelegate([SizedBox()]),
  369. ));
  370. slivers.add(EmptyWidget(
  371. child: widget.emptyWidget,
  372. ));
  373. }
  374. // 插入Header和Footer
  375. if (header != null && slivers != null)
  376. slivers.insert(widget.headerIndex ?? 0, header);
  377. if (footer != null && slivers != null) slivers.add(footer);
  378. }
  379. // 构建列表组件
  380. Widget listBody;
  381. if (widget.builder != null) {
  382. listBody = widget.builder(context, _physics, header, footer);
  383. } else if (widget.slivers != null) {
  384. listBody = CustomScrollView(
  385. key: widget.listKey,
  386. physics: _physics,
  387. slivers: slivers,
  388. scrollDirection: widget.scrollDirection,
  389. reverse: widget.reverse,
  390. controller: widget.scrollController,
  391. primary: widget.primary,
  392. shrinkWrap: widget.shrinkWrap,
  393. center: widget.center,
  394. anchor: widget.anchor,
  395. cacheExtent: widget.cacheExtent,
  396. semanticChildCount: widget.semanticChildCount,
  397. dragStartBehavior: widget.dragStartBehavior,
  398. );
  399. } else if (widget.child != null) {
  400. listBody = _buildListBodyByChild(slivers, header, footer);
  401. } else {
  402. listBody = Container();
  403. }
  404. return ScrollNotificationListener(
  405. onNotification: (notification) {
  406. return false;
  407. },
  408. onFocus: (focus) {
  409. _focusNotifier.value = focus;
  410. },
  411. child: listBody,
  412. );
  413. }
  414. // 将child转换为CustomScrollView可用的slivers
  415. List<Widget> _buildSliversByChild() {
  416. Widget child = widget.child;
  417. List<Widget> slivers;
  418. if (child == null) return slivers;
  419. if (child is ScrollView) {
  420. if (child is BoxScrollView) {
  421. // ignore: invalid_use_of_protected_member
  422. Widget sliver = child.buildChildLayout(context);
  423. if (child.padding != null) {
  424. slivers = [SliverPadding(sliver: sliver, padding: child.padding)];
  425. } else {
  426. slivers = [sliver];
  427. }
  428. } else {
  429. // ignore: invalid_use_of_protected_member
  430. slivers = List.from(child.buildSlivers(context), growable: true);
  431. }
  432. } else if (child is SingleChildScrollView) {
  433. slivers = [
  434. SliverPadding(
  435. sliver: SliverList(
  436. delegate: SliverChildListDelegate([child.child]),
  437. ),
  438. padding: child.padding ?? EdgeInsets.all(0.0),
  439. ),
  440. ];
  441. } else if (child is! Scrollable) {
  442. slivers = [
  443. SliverToBoxAdapter(
  444. child: child,
  445. )
  446. ];
  447. }
  448. return slivers;
  449. }
  450. // 通过child构建列表组件
  451. Widget _buildListBodyByChild(
  452. List<Widget> slivers, Widget header, Widget footer) {
  453. Widget child = widget.child;
  454. if (child is ScrollView) {
  455. _childScrollController = child.controller;
  456. return CustomScrollView(
  457. physics: _physics,
  458. controller: child.controller ?? widget.scrollController,
  459. cacheExtent: child.cacheExtent,
  460. key: child.key,
  461. scrollDirection: child.scrollDirection,
  462. semanticChildCount: child.semanticChildCount,
  463. slivers: slivers,
  464. dragStartBehavior: child.dragStartBehavior,
  465. reverse: child.reverse,
  466. );
  467. } else if (child is SingleChildScrollView) {
  468. _childScrollController = child.controller;
  469. return CustomScrollView(
  470. physics: _physics,
  471. controller: child.controller ?? widget.scrollController,
  472. scrollDirection: child.scrollDirection,
  473. slivers: slivers,
  474. dragStartBehavior: child.dragStartBehavior,
  475. reverse: child.reverse,
  476. );
  477. } else if (child is Scrollable) {
  478. _childScrollController = child.controller;
  479. return Scrollable(
  480. physics: _physics,
  481. controller: child.controller ?? widget.scrollController,
  482. axisDirection: child.axisDirection,
  483. semanticChildCount: child.semanticChildCount,
  484. dragStartBehavior: child.dragStartBehavior,
  485. viewportBuilder: (context, position) {
  486. Viewport viewport = child.viewportBuilder(context, position);
  487. // 判断是否有空视图
  488. if (widget.emptyWidget != null) {
  489. if (viewport.children.length > (widget.headerIndex ?? 0) + 1) {
  490. viewport.children.removeRange(
  491. widget.headerIndex, viewport.children.length - 1);
  492. }
  493. // 添加空元素避免异常
  494. viewport.children.add(SliverList(
  495. delegate: SliverChildListDelegate([SizedBox()]),
  496. ));
  497. viewport.children.add(EmptyWidget(
  498. child: widget.emptyWidget,
  499. ));
  500. }
  501. if (header != null) {
  502. viewport.children.insert(widget.headerIndex ?? 0, header);
  503. }
  504. if (footer != null) {
  505. viewport.children.add(footer);
  506. }
  507. return viewport;
  508. },
  509. );
  510. } else {
  511. return CustomScrollView(
  512. physics: _physics,
  513. controller: widget.scrollController,
  514. slivers: slivers,
  515. );
  516. }
  517. }
  518. }
  519. // 任务状态
  520. class TaskState {
  521. bool refreshing;
  522. bool loading;
  523. bool refreshNoMore;
  524. bool loadNoMore;
  525. TaskState(
  526. {this.refreshing = false,
  527. this.loading = false,
  528. this.refreshNoMore = false,
  529. this.loadNoMore = false});
  530. TaskState copy(
  531. {bool refreshing, bool loading, bool refreshNoMore, bool loadNoMore}) {
  532. return TaskState(
  533. refreshing: refreshing ?? this.refreshing,
  534. loading: loading ?? this.loading,
  535. refreshNoMore: refreshNoMore ?? this.refreshNoMore,
  536. loadNoMore: loadNoMore ?? this.loadNoMore,
  537. );
  538. }
  539. }
  540. // 指示器越界
  541. class RefreshIndicator {
  542. Header header;
  543. Footer footer;
  544. RefreshIndicator({this.header, this.footer});
  545. RefreshIndicator copy({Header header, Footer footer}) {
  546. return RefreshIndicator(
  547. header: header ?? this.header,
  548. footer: footer ?? this.footer,
  549. );
  550. }
  551. }
  552. // Refresh控制器
  553. class RefreshController {
  554. // 触发刷新
  555. void callRefresh({Duration duration = const Duration(milliseconds: 300)}) {
  556. if (this._RefreshState != null) {
  557. this._RefreshState.callRefresh(duration: duration);
  558. }
  559. }
  560. // 触发加载
  561. void callLoad({Duration duration = const Duration(milliseconds: 300)}) {
  562. if (this._RefreshState != null) {
  563. this._RefreshState.callLoad(duration: duration);
  564. }
  565. }
  566. // 完成刷新
  567. FinishRefresh finishRefreshCallBack;
  568. void finishRefresh({
  569. bool success,
  570. bool noMore,
  571. }) {
  572. if (finishRefreshCallBack != null) {
  573. finishRefreshCallBack(success: success, noMore: noMore);
  574. }
  575. }
  576. // 完成加载
  577. FinishLoad finishLoadCallBack;
  578. void finishLoad({
  579. bool success,
  580. bool noMore,
  581. }) {
  582. if (finishLoadCallBack != null) {
  583. finishLoadCallBack(success: success, noMore: noMore);
  584. }
  585. }
  586. // 恢复刷新状态(用于没有更多后)
  587. VoidCallback resetRefreshStateCallBack;
  588. void resetRefreshState() {
  589. if (resetRefreshStateCallBack != null) {
  590. resetRefreshStateCallBack();
  591. }
  592. }
  593. // 恢复加载状态(用于没有更多后)
  594. VoidCallback resetLoadStateCallBack;
  595. void resetLoadState() {
  596. if (resetLoadStateCallBack != null) {
  597. resetLoadStateCallBack();
  598. }
  599. }
  600. // 状态
  601. // ignore: non_constant_identifier_names
  602. _PrettyRefresherState _RefreshState;
  603. // 绑定状态
  604. void _bindRefreshState(_PrettyRefresherState state) {
  605. this._RefreshState = state;
  606. }
  607. void dispose() {
  608. this._RefreshState = null;
  609. this.finishRefreshCallBack = null;
  610. this.finishLoadCallBack = null;
  611. this.resetLoadStateCallBack = null;
  612. this.resetRefreshStateCallBack = null;
  613. }
  614. }