load_state_layout.dart 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import 'package:cs_resources/generated/assets.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:cs_resources/constants/color_constants.dart';
  4. import 'my_load_image.dart';
  5. import 'my_text_view.dart';
  6. ///四种视图状态
  7. enum LoadState { State_Success, State_Error, State_Loading, State_Empty }
  8. ///根据不同状态来展示不同的视图
  9. class LoadStateLayout extends StatefulWidget {
  10. final LoadState state; //页面状态
  11. final Widget? successWidget; //成功视图
  12. final List<Widget>? successSliverWidget; //成功的滚动视图(Sliver的布局)
  13. final VoidCallback? errorRetry; //错误事件处理
  14. final Color? themeColor;
  15. String? errorMessage;
  16. LoadStateLayout({
  17. Key? key,
  18. this.state = LoadState.State_Loading, //默认为加载状态
  19. this.successWidget, //成功的布局 (二选一)
  20. this.successSliverWidget, //成功的滚动布局(二选一)
  21. this.errorMessage, //错误的信息展示
  22. this.errorRetry, //错误重试的事件
  23. this.themeColor, //主题颜色,Loading,文本颜色
  24. }) : super(key: key);
  25. @override
  26. _LoadStateLayoutState createState() => _LoadStateLayoutState();
  27. }
  28. class _LoadStateLayoutState extends State<LoadStateLayout> {
  29. @override
  30. Widget build(BuildContext context) {
  31. if (widget.successSliverWidget != null) {
  32. //如果有 successSliverWidget 就使用 Slivers 的方式布局
  33. return CustomScrollView(
  34. slivers: _buildSlivers(),
  35. );
  36. } else {
  37. //如果没有有 successSliverWidget 就使用默认布局的方式布局
  38. return SizedBox(
  39. width: double.infinity,
  40. height: double.infinity,
  41. child: _buildWidget,
  42. );
  43. }
  44. }
  45. //Slivers的布局
  46. List<Widget> _buildSlivers() {
  47. return _buildListWidget;
  48. }
  49. ///根据不同状态来显示不同的视图 (默认布局)
  50. Widget get _buildWidget {
  51. switch (widget.state) {
  52. case LoadState.State_Success:
  53. return widget.successWidget ?? const SizedBox();
  54. case LoadState.State_Error:
  55. return _errorView;
  56. case LoadState.State_Loading:
  57. return _loadingView;
  58. case LoadState.State_Empty:
  59. return _emptyView;
  60. default:
  61. return _loadingView;
  62. }
  63. }
  64. ///根据不同状态来显示不同的视图 (CustomScrollView)
  65. List<Widget> get _buildListWidget {
  66. switch (widget.state) {
  67. case LoadState.State_Success:
  68. return widget.successSliverWidget != null
  69. ? widget.successSliverWidget!
  70. : widget.successWidget != null
  71. ? [widget.successWidget!]
  72. : [const SizedBox()];
  73. case LoadState.State_Error:
  74. return [widget.successSliverWidget != null ? _warpStateLayout(_errorView) : _errorView];
  75. case LoadState.State_Loading:
  76. return [widget.successSliverWidget != null ? _warpStateLayout(_loadingView) : _loadingView];
  77. case LoadState.State_Empty:
  78. return [widget.successSliverWidget != null ? _warpStateLayout(_emptyView) : _emptyView];
  79. default:
  80. return [widget.successSliverWidget != null ? _warpStateLayout(_loadingView) : _loadingView];
  81. }
  82. }
  83. //如果父布局是 CustomScrollView 则使用 SliverFillViewport 包裹状态布局
  84. Widget _warpStateLayout(Widget widget) {
  85. return SliverFillViewport(
  86. delegate: SliverChildBuilderDelegate(
  87. (context, index) {
  88. return widget;
  89. },
  90. childCount: 1,
  91. ),
  92. );
  93. }
  94. // ===================================== 真正的状态布局 ↓ =====================================
  95. ///加载中视图
  96. Widget get _loadingView {
  97. return Container(
  98. width: double.infinity,
  99. height: double.infinity,
  100. alignment: Alignment.center,
  101. child: Column(
  102. mainAxisSize: MainAxisSize.max,
  103. mainAxisAlignment: MainAxisAlignment.center,
  104. crossAxisAlignment: CrossAxisAlignment.center,
  105. children: [
  106. CircularProgressIndicator(
  107. strokeWidth: 3,
  108. valueColor: AlwaysStoppedAnimation(widget.themeColor ?? const Color(0XFFD6E9F1)),
  109. ),
  110. MyTextView(
  111. 'Loading...',
  112. marginTop: 15,
  113. fontSize: 14,
  114. textColor: widget.themeColor ?? const Color(0XFFD6E9F1),
  115. )
  116. ],
  117. ),
  118. );
  119. }
  120. ///错误视图
  121. Widget get _errorView {
  122. return Container(
  123. width: double.infinity,
  124. height: double.infinity,
  125. alignment: Alignment.center,
  126. padding: const EdgeInsets.only(bottom: 10),
  127. child: GestureDetector(
  128. onTap: widget.errorRetry,
  129. child: Column(
  130. mainAxisSize: MainAxisSize.max,
  131. crossAxisAlignment: CrossAxisAlignment.center,
  132. mainAxisAlignment: MainAxisAlignment.center,
  133. children: <Widget>[
  134. const MyAssetImage(Assets.baseServicePageLoadError, width: 141, height: 117.5, fit: BoxFit.contain),
  135. MyTextView(
  136. widget.errorMessage ?? 'Data loading failed! Please refresh and try again',
  137. marginTop: 18,
  138. fontSize: 14,
  139. textColor: widget.themeColor ?? const Color(0XFFD6E9F1),
  140. ),
  141. ],
  142. )));
  143. }
  144. ///数据为空的视图
  145. Widget get _emptyView {
  146. return Container(
  147. width: double.infinity,
  148. height: double.infinity,
  149. alignment: Alignment.center,
  150. padding: const EdgeInsets.only(bottom: 10),
  151. child: Column(
  152. mainAxisSize: MainAxisSize.max,
  153. crossAxisAlignment: CrossAxisAlignment.center,
  154. mainAxisAlignment: MainAxisAlignment.center,
  155. children: <Widget>[
  156. const MyAssetImage(Assets.baseServicePageNoData, width: 123.5, height: 115.5, fit: BoxFit.contain),
  157. MyTextView(
  158. 'There is currently no content available',
  159. marginTop: 18,
  160. fontSize: 14,
  161. textColor: widget.themeColor ?? const Color(0XFFD6E9F1),
  162. ),
  163. ],
  164. ),
  165. );
  166. }
  167. }