load_state_layout.dart 6.2 KB

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