sliver_loading.dart 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. import 'dart:async';
  2. import 'dart:math';
  3. import 'package:eye_video/framework/uikit/refresher/pretty_refresher.dart';
  4. import 'package:flutter/rendering.dart';
  5. import 'package:flutter/scheduler.dart';
  6. import 'package:flutter/services.dart';
  7. import 'package:flutter/widgets.dart';
  8. class _SliverLoading extends SingleChildRenderObjectWidget {
  9. const _SliverLoading({
  10. Key? key,
  11. this.loadIndicatorLayoutExtent = 0.0,
  12. this.hasLayoutExtent = false,
  13. this.enableInfiniteLoad = true,
  14. this.footerFloat = false,
  15. this.axisDirectionNotifier,
  16. required this.infiniteLoad,
  17. required this.extraExtentNotifier,
  18. Widget child,
  19. }) : assert(loadIndicatorLayoutExtent != null),
  20. assert(loadIndicatorLayoutExtent >= 0.0),
  21. assert(hasLayoutExtent != null),
  22. super(key: key, child: child);
  23. // The amount of space the indicator should occupy in the sliver in a
  24. // resting state when in the refreshing mode.
  25. final double loadIndicatorLayoutExtent;
  26. // _RenderRefreshSliverLoad will paint the child in the available
  27. // space either way but this instructs the _RenderRefreshSliverLoad
  28. // on whether to also occupy any layoutExtent space or not.
  29. final bool hasLayoutExtent;
  30. /// 是否开启无限加载
  31. final bool enableInfiniteLoad;
  32. /// 无限加载回调
  33. final VoidCallback infiniteLoad;
  34. /// Footer浮动
  35. final bool footerFloat;
  36. /// 列表方向
  37. final ValueNotifier<AxisDirection> axisDirectionNotifier;
  38. // 列表为占满时多余长度
  39. final ValueNotifier<double> extraExtentNotifier;
  40. @override
  41. _RenderRefreshSliverLoad createRenderObject(BuildContext context) {
  42. return _RenderRefreshSliverLoad(
  43. loadIndicatorExtent: loadIndicatorLayoutExtent,
  44. hasLayoutExtent: hasLayoutExtent,
  45. enableInfiniteLoad: enableInfiniteLoad,
  46. infiniteLoad: infiniteLoad,
  47. extraExtentNotifier: extraExtentNotifier,
  48. footerFloat: footerFloat,
  49. axisDirectionNotifier: axisDirectionNotifier,
  50. );
  51. }
  52. @override
  53. void updateRenderObject(
  54. BuildContext context, covariant _RenderRefreshSliverLoad renderObject) {
  55. renderObject
  56. ..loadIndicatorLayoutExtent = loadIndicatorLayoutExtent
  57. ..hasLayoutExtent = hasLayoutExtent
  58. ..enableInfiniteLoad = enableInfiniteLoad
  59. ..footerFloat = footerFloat;
  60. }
  61. }
  62. // RenderSliver object that gives its child RenderBox object space to paint
  63. // in the overscrolled gap and may or may not hold that overscrolled gap
  64. // around the RenderBox depending on whether [layoutExtent] is set.
  65. //
  66. // The [layoutExtentOffsetCompensation] field keeps internal accounting to
  67. // prevent scroll position jumps as the [layoutExtent] is set and unset.
  68. class _RenderRefreshSliverLoad extends RenderSliverSingleBoxAdapter {
  69. _RenderRefreshSliverLoad({
  70. required double loadIndicatorExtent,
  71. required bool hasLayoutExtent,
  72. required bool enableInfiniteLoad,
  73. required this.infiniteLoad,
  74. required this.extraExtentNotifier,
  75. required this.axisDirectionNotifier,
  76. required bool footerFloat,
  77. RenderBox child,
  78. }) : assert(loadIndicatorExtent != null),
  79. assert(loadIndicatorExtent >= 0.0),
  80. assert(hasLayoutExtent != null),
  81. _loadIndicatorExtent = loadIndicatorExtent,
  82. _enableInfiniteLoad = enableInfiniteLoad,
  83. _hasLayoutExtent = hasLayoutExtent,
  84. _footerFloat = footerFloat {
  85. this.child = child;
  86. }
  87. /// 列表方向
  88. final ValueNotifier<AxisDirection> axisDirectionNotifier;
  89. // The amount of layout space the indicator should occupy in the sliver in a
  90. // resting state when in the refreshing mode.
  91. double get loadIndicatorLayoutExtent => _loadIndicatorExtent;
  92. double _loadIndicatorExtent;
  93. set loadIndicatorLayoutExtent(double value) {
  94. assert(value != null);
  95. assert(value >= 0.0);
  96. if (value == _loadIndicatorExtent) return;
  97. _loadIndicatorExtent = value;
  98. markNeedsLayout();
  99. }
  100. // The child box will be laid out and painted in the available space either
  101. // way but this determines whether to also occupy any
  102. // [SliverGeometry.layoutExtent] space or not.
  103. bool get hasLayoutExtent => _hasLayoutExtent;
  104. bool _hasLayoutExtent;
  105. set hasLayoutExtent(bool value) {
  106. assert(value != null);
  107. if (value == _hasLayoutExtent) return;
  108. _hasLayoutExtent = value;
  109. markNeedsLayout();
  110. }
  111. /// 是否开启无限加载
  112. bool get enableInfiniteLoad => _enableInfiniteLoad;
  113. bool _enableInfiniteLoad;
  114. set enableInfiniteLoad(bool value) {
  115. assert(value != null);
  116. if (value == _enableInfiniteLoad) return;
  117. _enableInfiniteLoad = value;
  118. markNeedsLayout();
  119. }
  120. /// Header是否浮动
  121. bool get footerFloat => _footerFloat;
  122. bool _footerFloat;
  123. set footerFloat(bool value) {
  124. assert(value != null);
  125. if (value == _footerFloat) return;
  126. _footerFloat = value;
  127. markNeedsLayout();
  128. }
  129. /// 无限加载回调
  130. final VoidCallback infiniteLoad;
  131. // 列表为占满时多余长度
  132. final ValueNotifier<double> extraExtentNotifier;
  133. // 触发无限加载
  134. bool _triggerInfiniteLoad = false;
  135. // 获取子组件大小
  136. double get childSize =>
  137. constraints.axis == Axis.vertical ? child.size.height : child.size.width;
  138. // This keeps track of the previously applied scroll offsets to the scrollable
  139. // so that when [loadIndicatorLayoutExtent] or [hasLayoutExtent] changes,
  140. // the appropriate delta can be applied to keep everything in the same place
  141. // visually.
  142. double layoutExtentOffsetCompensation = 0.0;
  143. @override
  144. void performLayout() {
  145. // 判断列表是否未占满,去掉未占满高度
  146. double extraExtent = 0.0;
  147. if (constraints.precedingScrollExtent <
  148. constraints.viewportMainAxisExtent) {
  149. extraExtent = constraints.viewportMainAxisExtent -
  150. constraints.precedingScrollExtent;
  151. }
  152. extraExtentNotifier.value = extraExtent;
  153. // Only pulling to refresh from the top is currently supported.
  154. // 注释以支持reverse
  155. // assert(constraints.axisDirection == AxisDirection.down);
  156. assert(constraints.growthDirection == GrowthDirection.forward);
  157. // 判断是否触发无限加载
  158. if ((enableInfiniteLoad &&
  159. extraExtentNotifier.value < constraints.remainingPaintExtent ||
  160. (extraExtentNotifier.value == constraints.remainingPaintExtent &&
  161. constraints.cacheOrigin < 0.0)) &&
  162. constraints.remainingPaintExtent > 1.0) {
  163. if (!_triggerInfiniteLoad) {
  164. _triggerInfiniteLoad = true;
  165. infiniteLoad();
  166. }
  167. } else {
  168. if (constraints.remainingPaintExtent <= 1.0 ||
  169. extraExtent > 0.0 ||
  170. (enableInfiniteLoad &&
  171. extraExtentNotifier.value == constraints.remainingPaintExtent)) {
  172. if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
  173. _triggerInfiniteLoad = false;
  174. } else {
  175. SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
  176. _triggerInfiniteLoad = false;
  177. });
  178. }
  179. }
  180. }
  181. // The layout extent this sliver should now have.
  182. final double layoutExtent =
  183. (_hasLayoutExtent || enableInfiniteLoad ? 1.0 : 0.0) *
  184. _loadIndicatorExtent;
  185. // If the layoutExtent instructive changed, the SliverGeometry's
  186. // layoutExtent will take that value (on the next performLayout run). Shift
  187. // the scroll offset first so it doesn't make the scroll position suddenly jump.
  188. /*if (layoutExtent != layoutExtentOffsetCompensation) {
  189. geometry = SliverGeometry(
  190. scrollOffsetCorrection: layoutExtent - layoutExtentOffsetCompensation,
  191. );
  192. layoutExtentOffsetCompensation = layoutExtent;
  193. // Return so we don't have to do temporary accounting and adjusting the
  194. // child's constraints accounting for this one transient frame using a
  195. // combination of existing layout extent, layout extent change and
  196. // the overlap.
  197. return;
  198. }*/
  199. final bool active = (constraints.remainingPaintExtent > 1.0 ||
  200. layoutExtent >=
  201. (enableInfiniteLoad ? 1.0 : 0.0) * _loadIndicatorExtent);
  202. // 如果列表已有范围不大于指示器的范围则加上滚动超出距离
  203. final double overscrolledExtent = max(
  204. constraints.remainingPaintExtent +
  205. (constraints.precedingScrollExtent < _loadIndicatorExtent
  206. ? constraints.scrollOffset
  207. : 0.0),
  208. 0.0);
  209. // 是否反向
  210. bool isReverse = constraints.axisDirection == AxisDirection.up ||
  211. constraints.axisDirection == AxisDirection.left;
  212. axisDirectionNotifier.value = constraints.axisDirection;
  213. // Layout the child giving it the space of the currently dragged overscroll
  214. // which may or may not include a sliver layout extent space that it will
  215. // keep after the user lets go during the refresh process.
  216. child.layout(
  217. constraints.asBoxConstraints(
  218. maxExtent: isReverse
  219. ? overscrolledExtent
  220. : _hasLayoutExtent || enableInfiniteLoad
  221. ? _loadIndicatorExtent > overscrolledExtent
  222. ? _loadIndicatorExtent
  223. : overscrolledExtent
  224. : overscrolledExtent,
  225. ),
  226. parentUsesSize: true,
  227. );
  228. if (active) {
  229. geometry = SliverGeometry(
  230. scrollExtent: layoutExtent,
  231. paintOrigin: -constraints.scrollOffset,
  232. paintExtent: max(
  233. // Check child size (which can come from overscroll) because
  234. // layoutExtent may be zero. Check layoutExtent also since even
  235. // with a layoutExtent, the indicator builder may decide to not
  236. // build anything.
  237. min(max(childSize, layoutExtent), constraints.remainingPaintExtent) -
  238. constraints.scrollOffset,
  239. 0.0,
  240. ),
  241. maxPaintExtent: max(
  242. min(max(childSize, layoutExtent), constraints.remainingPaintExtent) -
  243. constraints.scrollOffset,
  244. 0.0,
  245. ),
  246. layoutExtent: min(max(layoutExtent - constraints.scrollOffset, 0.0),
  247. constraints.remainingPaintExtent),
  248. );
  249. } else {
  250. // If we never started overscrolling, return no geometry.
  251. geometry = SliverGeometry.zero;
  252. }
  253. }
  254. @override
  255. void paint(PaintingContext paintContext, Offset offset) {
  256. if (constraints.remainingPaintExtent > 0.0 ||
  257. constraints.scrollOffset + childSize > 0) {
  258. paintContext.paintChild(child, offset);
  259. }
  260. }
  261. // Nothing special done here because this sliver always paints its child
  262. // exactly between paintOrigin and paintExtent.
  263. @override
  264. void applyPaintTransform(RenderObject child, Matrix4 transform) {}
  265. }
  266. /// The current state of the refresh control.
  267. ///
  268. /// Passed into the [LoadControlBuilder] builder function so
  269. /// users can show different UI in different modes.
  270. enum LoadMode {
  271. /// Initial state, when not being overscrolled into, or after the overscroll
  272. /// is canceled or after done and the sliver retracted away.
  273. inactive,
  274. /// While being overscrolled but not far enough yet to trigger the refresh.
  275. drag,
  276. /// Dragged far enough that the onLoad callback will run and the dragged
  277. /// displacement is not yet at the final refresh resting state.
  278. armed,
  279. /// While the onLoad task is running.
  280. load,
  281. /// 刷新完成
  282. loaded,
  283. /// While the indicator is animating away after refreshing.
  284. done,
  285. }
  286. /// Signature for a builder that can create a different widget to show in the
  287. /// refresh indicator space depending on the current state of the refresh
  288. /// control and the space available.
  289. ///
  290. /// The `loadTriggerPullDistance` and `loadIndicatorExtent` parameters are
  291. /// the same values passed into the [RefreshSliverLoadControl].
  292. ///
  293. /// The `pulledExtent` parameter is the currently available space either from
  294. /// overscrolling or as held by the sliver during refresh.
  295. typedef LoadControlBuilder = Widget Function(
  296. BuildContext context,
  297. LoadMode loadState,
  298. double pulledExtent,
  299. double loadTriggerPullDistance,
  300. double loadIndicatorExtent,
  301. AxisDirection axisDirection,
  302. bool float,
  303. Duration completeDuration,
  304. bool enableInfiniteLoad,
  305. bool success,
  306. bool noMore);
  307. /// A callback function that's invoked when the [RefreshSliverLoadControl] is
  308. /// pulled a `loadTriggerPullDistance`. Must return a [Future]. Upon
  309. /// completion of the [Future], the [RefreshSliverLoadControl] enters the
  310. /// [LoadMode.done] state and will start to go away.
  311. typedef OnLoadCallback = Future<void> Function();
  312. /// 结束加载
  313. /// success 为是否成功(为false时,noMore无效)
  314. /// noMore 为是否有更多数据
  315. typedef FinishLoad = void Function({
  316. bool success,
  317. bool noMore,
  318. });
  319. /// 绑定加载指示剂
  320. typedef BindLoadIndicator = void Function(
  321. FinishLoad finishLoad, VoidCallback resetLoadState);
  322. /// A sliver widget implementing the iOS-style pull to refresh content control.
  323. ///
  324. /// When inserted as the first sliver in a scroll view or behind other slivers
  325. /// that still lets the scrollable overscroll in front of this sliver (such as
  326. /// the [CupertinoSliverNavigationBar], this widget will:
  327. ///
  328. /// * Let the user draw inside the overscrolled area via the passed in [builder].
  329. /// * Trigger the provided [onLoad] function when overscrolled far enough to
  330. /// pass [loadTriggerPullDistance].
  331. /// * Continue to hold [loadIndicatorExtent] amount of space for the [builder]
  332. /// to keep drawing inside of as the [Future] returned by [onLoad] processes.
  333. /// * Scroll away once the [onLoad] [Future] completes.
  334. ///
  335. /// The [builder] function will be informed of the current [LoadMode]
  336. /// when invoking it, except in the [LoadMode.inactive] state when
  337. /// no space is available and nothing needs to be built. The [builder] function
  338. /// will otherwise be continuously invoked as the amount of space available
  339. /// changes from overscroll, as the sliver scrolls away after the [onLoad]
  340. /// task is done, etc.
  341. ///
  342. /// Only one refresh can be triggered until the previous refresh has completed
  343. /// and the indicator sliver has retracted at least 90% of the way back.
  344. ///
  345. /// Can only be used in downward-scrolling vertical lists that overscrolls. In
  346. /// other words, refreshes can't be triggered with lists using
  347. /// [ClampingScrollPhysics].
  348. ///
  349. /// In a typical application, this sliver should be inserted between the app bar
  350. /// sliver such as [CupertinoSliverNavigationBar] and your main scrollable
  351. /// content's sliver.
  352. ///
  353. /// See also:
  354. ///
  355. /// * [CustomScrollView], a typical sliver holding scroll view this control
  356. /// should go into.
  357. /// * <https://developer.apple.com/ios/human-interface-guidelines/controls/refresh-content-controls/>
  358. /// * [RefreshIndicator], a Material Design version of the pull-to-refresh
  359. /// paradigm. This widget works differently than [RefreshIndicator] because
  360. /// instead of being an overlay on top of the scrollable, the
  361. /// [RefreshSliverLoadControl] is part of the scrollable and actively occupies
  362. /// scrollable space.
  363. class RefreshSliverLoadControl extends StatefulWidget {
  364. /// Create a refresh control for inserting into a list of slivers.
  365. ///
  366. /// The [loadTriggerPullDistance] and [loadIndicatorExtent] arguments
  367. /// must not be null and must be >= 0.
  368. ///
  369. /// The [builder] argument may be null, in which case no indicator UI will be
  370. /// shown but the [onLoad] will still be invoked. By default, [builder]
  371. /// shows a [CupertinoActivityIndicator].
  372. ///
  373. /// The [onLoad] argument will be called when pulled far enough to trigger
  374. /// a refresh.
  375. const RefreshSliverLoadControl({
  376. Key? key,
  377. this.loadTriggerPullDistance = _defaultLoadTriggerPullDistance,
  378. this.loadIndicatorExtent = _defaultLoadIndicatorExtent,
  379. required this.builder,
  380. this.completeDuration,
  381. this.onLoad,
  382. this.focusNotifier,
  383. this.taskNotifier,
  384. this.callLoadNotifier,
  385. this.taskIndependence,
  386. this.bindLoadIndicator,
  387. this.enableControlFinishLoad = false,
  388. this.enableInfiniteLoad = true,
  389. this.enableHapticFeedback = false,
  390. this.footerFloat = false,
  391. }) : assert(loadTriggerPullDistance != null),
  392. assert(loadTriggerPullDistance > 0.0),
  393. assert(loadIndicatorExtent != null),
  394. assert(loadIndicatorExtent >= 0.0),
  395. assert(
  396. loadTriggerPullDistance >= loadIndicatorExtent,
  397. 'The refresh indicator cannot take more space in its final state '
  398. 'than the amount initially created by overscrolling.'),
  399. super(key: key);
  400. /// The amount of overscroll the scrollable must be dragged to trigger a reload.
  401. ///
  402. /// Must not be null, must be larger than 0.0 and larger than
  403. /// [loadIndicatorExtent]. Defaults to 100px when not specified.
  404. ///
  405. /// When overscrolled past this distance, [onLoad] will be called if not
  406. /// null and the [builder] will build in the [LoadMode.armed] state.
  407. final double loadTriggerPullDistance;
  408. /// The amount of space the refresh indicator sliver will keep holding while
  409. /// [onLoad]'s [Future] is still running.
  410. ///
  411. /// Must not be null and must be positive, but can be 0.0, in which case the
  412. /// sliver will start retracting back to 0.0 as soon as the refresh is started.
  413. /// Defaults to 60px when not specified.
  414. ///
  415. /// Must be smaller than [loadTriggerPullDistance], since the sliver
  416. /// shouldn't grow further after triggering the refresh.
  417. final double loadIndicatorExtent;
  418. /// A builder that's called as this sliver's size changes, and as the state
  419. /// changes.
  420. ///
  421. /// A default simple Twitter-style pull-to-refresh indicator is provided if
  422. /// not specified.
  423. ///
  424. /// Can be set to null, in which case nothing will be drawn in the overscrolled
  425. /// space.
  426. ///
  427. /// Will not be called when the available space is zero such as before any
  428. /// overscroll.
  429. final LoadControlBuilder builder;
  430. /// Callback invoked when pulled by [loadTriggerPullDistance].
  431. ///
  432. /// If provided, must return a [Future] which will keep the indicator in the
  433. /// [LoadMode.refresh] state until the [Future] completes.
  434. ///
  435. /// Can be null, in which case a single frame of [LoadMode.armed]
  436. /// state will be drawn before going immediately to the [LoadMode.done]
  437. /// where the sliver will start retracting.
  438. final OnLoadCallback onLoad;
  439. /// 完成延时
  440. final Duration completeDuration;
  441. /// 绑定加载指示器
  442. final BindLoadIndicator bindLoadIndicator;
  443. /// 是否开启控制结束
  444. final bool enableControlFinishLoad;
  445. /// 是否开启无限加载
  446. final bool enableInfiniteLoad;
  447. /// 开启震动反馈
  448. final bool enableHapticFeedback;
  449. /// 滚动状态
  450. final ValueNotifier<bool> focusNotifier;
  451. /// 任务状态
  452. final ValueNotifier<TaskState> taskNotifier;
  453. // 触发加载状态
  454. final ValueNotifier<bool> callLoadNotifier;
  455. /// 是否任务独立
  456. final bool taskIndependence;
  457. /// Footer浮动
  458. final bool footerFloat;
  459. static const double _defaultLoadTriggerPullDistance = 100.0;
  460. static const double _defaultLoadIndicatorExtent = 60.0;
  461. /// Retrieve the current state of the RefreshSliverLoadControl. The same as the
  462. /// state that gets passed into the [builder] function. Used for testing.
  463. /*@visibleForTesting
  464. static LoadMode state(BuildContext context) {
  465. final _RefreshSliverLoadControlState state =
  466. context.findAncestorStateOfType<_RefreshSliverLoadControlState>();
  467. return state.loadState;
  468. }*/
  469. @override
  470. _RefreshSliverLoadControlState createState() =>
  471. _RefreshSliverLoadControlState();
  472. }
  473. class _RefreshSliverLoadControlState extends State<RefreshSliverLoadControl> {
  474. // Reset the state from done to inactive when only this fraction of the
  475. // original `loadTriggerPullDistance` is left.
  476. static const double _inactiveResetOverscrollFraction = 0.1;
  477. LoadMode loadState;
  478. // [Future] returned by the widget's `onLoad`.
  479. Future<void> _loadTask;
  480. Future<void> get loadTask => _loadTask;
  481. bool get hasTask {
  482. return widget.taskIndependence
  483. ? _loadTask != null
  484. : widget.taskNotifier.value.refreshing ||
  485. widget.taskNotifier.value.loading;
  486. }
  487. set loadTask(Future<void> task) {
  488. _loadTask = task;
  489. if (!widget.taskIndependence)
  490. widget.taskNotifier.value =
  491. widget.taskNotifier.value.copy(loading: task != null);
  492. }
  493. // The amount of space available from the inner indicator box's perspective.
  494. //
  495. // The value is the sum of the sliver's layout extent and the overscroll
  496. // (which partially gets transferred into the layout extent when the refresh
  497. // triggers).
  498. //
  499. // The value of latestIndicatorBoxExtent doesn't change when the sliver scrolls
  500. // away without retracting; it is independent from the sliver's scrollOffset.
  501. double latestIndicatorBoxExtent = 0.0;
  502. bool hasSliverLayoutExtent = false;
  503. // 滚动焦点
  504. bool get _focus => widget.focusNotifier.value;
  505. // 刷新完成
  506. bool _success;
  507. // 没有更多数据
  508. bool _noMore;
  509. // 列表为占满时多余长度
  510. ValueNotifier<double> extraExtentNotifier;
  511. // 列表方向
  512. ValueNotifier<AxisDirection> _axisDirectionNotifier;
  513. // 初始化
  514. @override
  515. void initState() {
  516. super.initState();
  517. loadState = LoadMode.inactive;
  518. extraExtentNotifier = ValueNotifier<double>(0.0);
  519. _axisDirectionNotifier = ValueNotifier<AxisDirection>(AxisDirection.down);
  520. // 绑定加载指示器
  521. if (widget.bindLoadIndicator != null) {
  522. widget.bindLoadIndicator(finishLoad, resetLoadState);
  523. }
  524. }
  525. // 销毁
  526. @override
  527. void dispose() {
  528. super.dispose();
  529. extraExtentNotifier.dispose();
  530. }
  531. // 完成刷新
  532. void finishLoad({
  533. bool success = true,
  534. bool noMore = false,
  535. }) {
  536. _success = success;
  537. _noMore = _success == false ? false : noMore;
  538. widget.taskNotifier.value =
  539. widget.taskNotifier.value.copy(loadNoMore: _noMore);
  540. if (widget.enableControlFinishLoad && loadTask != null) {
  541. if (widget.enableInfiniteLoad) {
  542. loadState = LoadMode.inactive;
  543. }
  544. setState(() => loadTask = null);
  545. loadState = transitionNextState();
  546. }
  547. }
  548. // 恢复状态
  549. void resetLoadState() {
  550. if (mounted) {
  551. setState(() {
  552. _success = true;
  553. _noMore = false;
  554. loadState = LoadMode.inactive;
  555. hasSliverLayoutExtent = false;
  556. });
  557. }
  558. }
  559. // 无限加载
  560. void _infiniteLoad() {
  561. if (!hasTask &&
  562. widget.enableInfiniteLoad &&
  563. _noMore != true &&
  564. !widget.callLoadNotifier.value) {
  565. if (widget.enableHapticFeedback) {
  566. HapticFeedback.mediumImpact();
  567. }
  568. SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
  569. loadState = LoadMode.load;
  570. loadTask = widget.onLoad()
  571. ..then((_) {
  572. if (mounted && !widget.enableControlFinishLoad) {
  573. loadState = LoadMode.load;
  574. setState(() => loadTask = null);
  575. // Trigger one more transition because by this time, BoxConstraint's
  576. // maxHeight might already be resting at 0 in which case no
  577. // calls to [transitionNextState] will occur anymore and the
  578. // state may be stuck in a non-inactive state.
  579. loadState = transitionNextState();
  580. }
  581. });
  582. setState(() => hasSliverLayoutExtent = true);
  583. });
  584. }
  585. }
  586. // A state machine transition calculator. Multiple states can be transitioned
  587. // through per single call.
  588. LoadMode transitionNextState() {
  589. LoadMode nextState;
  590. // 判断是否没有更多
  591. if (_noMore == true && widget.enableInfiniteLoad) {
  592. return loadState;
  593. } else if (_noMore == true &&
  594. loadState != LoadMode.load &&
  595. loadState != LoadMode.loaded &&
  596. loadState != LoadMode.done) {
  597. return loadState;
  598. } else if (widget.enableInfiniteLoad && loadState == LoadMode.done) {
  599. return LoadMode.inactive;
  600. }
  601. // 完成
  602. void goToDone() {
  603. nextState = LoadMode.done;
  604. loadState = LoadMode.done;
  605. // Either schedule the RenderSliver to re-layout on the next frame
  606. // when not currently in a frame or schedule it on the next frame.
  607. if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
  608. setState(() => hasSliverLayoutExtent = false);
  609. } else {
  610. SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
  611. setState(() => hasSliverLayoutExtent = false);
  612. });
  613. }
  614. }
  615. // 结束
  616. LoadMode goToFinish() {
  617. // 判断加载完成
  618. LoadMode state = LoadMode.loaded;
  619. // 添加延时
  620. if (widget.completeDuration == null || widget.enableInfiniteLoad) {
  621. goToDone();
  622. return null;
  623. } else {
  624. Future.delayed(widget.completeDuration, () {
  625. if (mounted) {
  626. goToDone();
  627. }
  628. });
  629. return state;
  630. }
  631. }
  632. switch (loadState) {
  633. case LoadMode.inactive:
  634. if (latestIndicatorBoxExtent <= 0 ||
  635. (!_focus && !widget.callLoadNotifier.value)) {
  636. return LoadMode.inactive;
  637. } else {
  638. nextState = LoadMode.drag;
  639. }
  640. continue drag;
  641. drag:
  642. case LoadMode.drag:
  643. if (latestIndicatorBoxExtent == 0) {
  644. return LoadMode.inactive;
  645. } else if (latestIndicatorBoxExtent <= widget.loadTriggerPullDistance) {
  646. // 如果未触发加载则取消固定高度
  647. if (hasSliverLayoutExtent && !hasTask) {
  648. SchedulerBinding.instance
  649. .addPostFrameCallback((Duration timestamp) {
  650. setState(() => hasSliverLayoutExtent = false);
  651. });
  652. }
  653. return LoadMode.drag;
  654. } else {
  655. // 提前固定高度,防止列表回弹
  656. SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
  657. setState(() => hasSliverLayoutExtent = true);
  658. });
  659. if (widget.onLoad != null && !hasTask) {
  660. if (!_focus) {
  661. if (widget.callLoadNotifier.value) {
  662. widget.callLoadNotifier.value = false;
  663. }
  664. if (widget.enableHapticFeedback) {
  665. HapticFeedback.mediumImpact();
  666. }
  667. // 触发加载任务
  668. SchedulerBinding.instance
  669. .addPostFrameCallback((Duration timestamp) {
  670. loadTask = widget.onLoad()
  671. ..then((_) {
  672. if (mounted && !widget.enableControlFinishLoad) {
  673. if (widget.enableInfiniteLoad) {
  674. loadState = LoadMode.inactive;
  675. }
  676. setState(() => loadTask = null);
  677. if (!widget.enableInfiniteLoad)
  678. loadState = transitionNextState();
  679. }
  680. });
  681. });
  682. return LoadMode.armed;
  683. }
  684. return LoadMode.drag;
  685. }
  686. return LoadMode.drag;
  687. }
  688. // Don't continue here. We can never possibly call onLoad and
  689. // progress to the next state in one [computeNextState] call.
  690. break;
  691. case LoadMode.armed:
  692. if (loadState == LoadMode.armed && !hasTask) {
  693. // 结束
  694. var state = goToFinish();
  695. if (state != null) return state;
  696. continue done;
  697. }
  698. if (latestIndicatorBoxExtent > widget.loadIndicatorExtent) {
  699. return LoadMode.armed;
  700. } else {
  701. nextState = LoadMode.load;
  702. }
  703. continue refresh;
  704. refresh:
  705. case LoadMode.load:
  706. if (loadTask != null) {
  707. return LoadMode.load;
  708. } else {
  709. // 结束
  710. var state = goToFinish();
  711. if (state != null) return state;
  712. }
  713. continue done;
  714. done:
  715. case LoadMode.done:
  716. // Let the transition back to inactive trigger before strictly going
  717. // to 0.0 since the last bit of the animation can take some time and
  718. // can feel sluggish if not going all the way back to 0.0 prevented
  719. // a subsequent pull-to-refresh from starting.
  720. if (latestIndicatorBoxExtent >
  721. widget.loadTriggerPullDistance * _inactiveResetOverscrollFraction) {
  722. return LoadMode.done;
  723. } else {
  724. nextState = LoadMode.inactive;
  725. }
  726. break;
  727. case LoadMode.loaded:
  728. nextState = loadState;
  729. break;
  730. default:
  731. break;
  732. }
  733. return nextState;
  734. }
  735. @override
  736. Widget build(BuildContext context) {
  737. return _SliverLoading(
  738. loadIndicatorLayoutExtent: widget.loadIndicatorExtent,
  739. hasLayoutExtent: hasSliverLayoutExtent,
  740. enableInfiniteLoad: widget.enableInfiniteLoad,
  741. infiniteLoad: _infiniteLoad,
  742. extraExtentNotifier: extraExtentNotifier,
  743. footerFloat: widget.footerFloat,
  744. axisDirectionNotifier: _axisDirectionNotifier,
  745. // A LayoutBuilder lets the sliver's layout changes be fed back out to
  746. // its owner to trigger state changes.
  747. child: LayoutBuilder(
  748. builder: (BuildContext context, BoxConstraints constraints) {
  749. // 判断是否有刷新任务
  750. if (!widget.taskIndependence &&
  751. widget.taskNotifier.value.refreshing) {
  752. return SizedBox();
  753. }
  754. // 是否为垂直方向
  755. bool isVertical =
  756. _axisDirectionNotifier.value == AxisDirection.down ||
  757. _axisDirectionNotifier.value == AxisDirection.up;
  758. // 是否反向
  759. bool isReverse = _axisDirectionNotifier.value == AxisDirection.up ||
  760. _axisDirectionNotifier.value == AxisDirection.left;
  761. latestIndicatorBoxExtent =
  762. (isVertical ? constraints.maxHeight : constraints.maxWidth) -
  763. extraExtentNotifier.value;
  764. loadState = transitionNextState();
  765. // 列表未占满时恢复一下状态
  766. if (extraExtentNotifier.value > 0.0 &&
  767. loadState == LoadMode.loaded &&
  768. loadTask == null) {
  769. loadState = LoadMode.inactive;
  770. }
  771. if (widget.builder != null && latestIndicatorBoxExtent >= 0) {
  772. Widget child = widget.builder(
  773. context,
  774. loadState,
  775. latestIndicatorBoxExtent,
  776. widget.loadTriggerPullDistance,
  777. widget.loadIndicatorExtent,
  778. _axisDirectionNotifier.value,
  779. widget.footerFloat,
  780. widget.completeDuration,
  781. widget.enableInfiniteLoad,
  782. _success ?? true,
  783. _noMore ?? false,
  784. );
  785. // 顶出列表未占满多余部分
  786. return isVertical
  787. ? Column(
  788. children: [
  789. isReverse
  790. ? SizedBox()
  791. : Expanded(
  792. flex: 1,
  793. child: SizedBox(),
  794. ),
  795. Container(
  796. height: latestIndicatorBoxExtent,
  797. child: child,
  798. ),
  799. !isReverse
  800. ? SizedBox()
  801. : Expanded(
  802. flex: 1,
  803. child: SizedBox(),
  804. ),
  805. ],
  806. )
  807. : Row(
  808. children: [
  809. isReverse
  810. ? SizedBox()
  811. : Expanded(
  812. flex: 1,
  813. child: SizedBox(),
  814. ),
  815. Container(
  816. width: latestIndicatorBoxExtent,
  817. child: child,
  818. ),
  819. !isReverse
  820. ? SizedBox()
  821. : Expanded(
  822. flex: 1,
  823. child: SizedBox(),
  824. ),
  825. ],
  826. );
  827. }
  828. return Container();
  829. },
  830. ),
  831. );
  832. }
  833. }