load_state_layout.dart 6.4 KB

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