基础组件库
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.
 
 
 
 
 

393 Zeilen
13 KiB

  1. import 'dart:convert';
  2. import 'package:flutter/cupertino.dart';
  3. import 'package:pull_to_refresh/pull_to_refresh.dart';
  4. import 'package:tab_indicator_styler/tab_indicator_styler.dart';
  5. import 'package:zhiying_base_widget/pages/custom_page/custom_item_page.dart';
  6. import 'package:zhiying_base_widget/pages/main_page/notifier/main_page_bg_notifier.dart';
  7. import 'package:zhiying_base_widget/widgets/custom/search/custom_search_widget.dart';
  8. import 'package:zhiying_base_widget/widgets/empty/empty_widget.dart';
  9. import 'package:zhiying_base_widget/widgets/others/mine_header_bg_widget.dart';
  10. import 'package:zhiying_comm/zhiying_comm.dart';
  11. import 'package:flutter/material.dart';
  12. import 'package:provider/provider.dart';
  13. import 'package:flutter_bloc/flutter_bloc.dart';
  14. import 'bloc/custom_page_bloc.dart';
  15. import 'bloc/custom_page_state.dart';
  16. import 'bloc/custom_page_event.dart';
  17. import 'bloc/custom_page_repository.dart';
  18. import 'dart:ui';
  19. import 'package:fluttertoast/fluttertoast.dart';
  20. ///
  21. /// 通用模块页面
  22. ///
  23. class CustomPage extends StatefulWidget {
  24. final Map<String, dynamic> data;
  25. CustomPage(this.data, {Key key}) : super(key: key);
  26. @override
  27. _CustomPageState createState() => _CustomPageState();
  28. }
  29. class _CustomPageState extends State<CustomPage> {
  30. @override
  31. Widget build(BuildContext context) {
  32. return MultiProvider(
  33. providers: [
  34. ChangeNotifierProvider.value(value: MainPageBgNotifier()),
  35. ],
  36. child: BlocProvider<CustomPageBloc>(
  37. create: (_) => CustomPageBloc(CustomPageRepository(data: widget?.data))..add(CustomPageInitEvent()),
  38. child: _CommonPageContainer(widget?.data),
  39. // ),
  40. ),
  41. );
  42. }
  43. }
  44. class _CommonPageContainer extends StatefulWidget {
  45. final Map<String, dynamic> data;
  46. _CommonPageContainer(this.data);
  47. @override
  48. __CommonPageContainerState createState() => __CommonPageContainerState();
  49. }
  50. class __CommonPageContainerState extends State<_CommonPageContainer> with SingleTickerProviderStateMixin {
  51. TabController _tabController;
  52. // 是否有AppBar
  53. bool _isHasAppbar = false;
  54. // 是否有TabBar
  55. bool _isHasTabBar = false;
  56. /// 刷新
  57. void _onRefreshEvent() async {
  58. BlocProvider.of<CustomPageBloc>(context).add(CustomPageRefreshEvent());
  59. }
  60. @override
  61. void dispose() {
  62. _tabController?.dispose();
  63. super.dispose();
  64. }
  65. @override
  66. Widget build(BuildContext context) {
  67. return MediaQuery.removePadding(
  68. context: context,
  69. child: BlocConsumer<CustomPageBloc, CustomPageState>(
  70. listener: (context, state) {},
  71. buildWhen: (prev, current) {
  72. if (current is CustomPageErrorState) {
  73. return false;
  74. }
  75. if (current is CustomPageRefreshSuccessState) {
  76. // _refreshController.refreshCompleted(resetFooterState: true);
  77. return false;
  78. }
  79. if (current is CustomPageRefreshErrorState) {
  80. // _refreshController.refreshFailed();
  81. return false;
  82. }
  83. return true;
  84. },
  85. builder: (context, state) {
  86. /// 有数据
  87. if (state is CustomPageLoadedState) {
  88. if (EmptyUtil.isEmpty(state.model)) return _buildEmptyWidget();
  89. return _buildMainWidget(state.model);
  90. }
  91. /// 初始化失败
  92. if (state is CustomPageInitErrorState) {
  93. return _buildEmptyWidget();
  94. }
  95. /// 骨架图
  96. return _buildSkeletonWidget();
  97. },
  98. ),
  99. );
  100. }
  101. /// 有数据
  102. Widget _buildMainWidget(List<Map<String, dynamic>> model) {
  103. return Stack(
  104. children: <Widget>[
  105. // 背景
  106. Column(
  107. children: <Widget>[
  108. // Container(
  109. // width: double.infinity,
  110. // height: 190,
  111. // color: HexColor.fromHex('#FF4242'),
  112. // ),
  113. Expanded(
  114. child: Container(
  115. height: double.infinity,
  116. width: double.infinity,
  117. color: HexColor.fromHex('#F9F9F9'),
  118. ),
  119. )
  120. ],
  121. ),
  122. // MineHeaderBgWidget(controller: null),
  123. Scaffold(
  124. appBar: _buildAppbar(model?.first),
  125. backgroundColor: Colors.transparent,
  126. // floatingActionButton: _buildFloatWidget(),
  127. floatingActionButtonLocation: _CustomFloatingActionButtonLocation(FloatingActionButtonLocation.endFloat, 0, -100),
  128. body: Column(children: _buildFirstWidget(model)),
  129. ),
  130. ],
  131. );
  132. }
  133. /// 骨架图
  134. Widget _buildSkeletonWidget() {
  135. return Scaffold();
  136. }
  137. /// 空数据视图
  138. Widget _buildEmptyWidget() {
  139. return Scaffold(
  140. backgroundColor: HexColor.fromHex('#F9F9F9'),
  141. appBar: AppBar(
  142. brightness: Brightness.light,
  143. backgroundColor: Colors.white,
  144. leading: IconButton(
  145. icon: Icon(
  146. Icons.arrow_back_ios,
  147. size: 22,
  148. color: HexColor.fromHex('#333333'),
  149. ),
  150. onPressed: () => Navigator.maybePop(context),
  151. ),
  152. title: Text(
  153. '',
  154. style: TextStyle(color: HexColor.fromHex('#333333'), fontSize: 18, fontWeight: FontWeight.bold),
  155. ),
  156. centerTitle: true,
  157. elevation: 0,
  158. ),
  159. body: Column(
  160. crossAxisAlignment: CrossAxisAlignment.center,
  161. mainAxisAlignment: MainAxisAlignment.center,
  162. children: <Widget>[
  163. Align(
  164. alignment: Alignment.topCenter,
  165. child: EmptyWidget(
  166. tips: '网络似乎开小差了~',
  167. ),
  168. ),
  169. GestureDetector(
  170. onTap: () => _onRefreshEvent(),
  171. behavior: HitTestBehavior.opaque,
  172. child: Container(
  173. alignment: Alignment.center,
  174. decoration: BoxDecoration(borderRadius: BorderRadius.circular(10), border: Border.all(color: HexColor.fromHex('#999999'), width: 0.5), color: Colors.white),
  175. width: 80,
  176. height: 20,
  177. child: Text(
  178. '刷新一下',
  179. style: TextStyle(fontSize: 12, color: HexColor.fromHex('#333333'), fontWeight: FontWeight.bold),
  180. ),
  181. ),
  182. )
  183. ],
  184. ));
  185. }
  186. /// 数据,生成第一层widget
  187. List<Widget> _buildFirstWidget(List<Map<String, dynamic>> model) {
  188. List<Widget> result = [];
  189. // 分类导航的key ⚠️ 这里先写成Test 后续要改
  190. const String CATEGORY_KEY = 'category';
  191. // 判断是否有分类导航
  192. // 判断最后一个是否属于分类导航,如果属于,则有分类导航,如果不是,则无分类导航
  193. bool haveCategory = !EmptyUtil.isEmpty(model?.last) && model.last.containsKey('mod_name') && model.last['mod_name'] == CATEGORY_KEY;
  194. int endIndexLength = model.length;
  195. // 如果没有分类导航,则取分类导航之上的所有mod
  196. if (!haveCategory) {
  197. for (int i = 0; i < model.length; i++) {
  198. Map<String, dynamic> item = model[i];
  199. if (item['mod_name'] == CATEGORY_KEY) {
  200. endIndexLength = (i + 1);
  201. break;
  202. }
  203. }
  204. }
  205. for (int i = 0; i < endIndexLength; i++) {
  206. WidgetModel item = WidgetModel.fromJson(Map<String, dynamic>.from(model[i]));
  207. // last model
  208. if (i == endIndexLength - 1) {
  209. result.addAll(_buildTabBar(model[i], i));
  210. break;
  211. }
  212. // appBar 无需在这里添加
  213. if (item.modName.contains('appbar')) {
  214. continue;
  215. }
  216. result.addAll(WidgetFactory.create(item.modName, isSliver: false, model: model[i]));
  217. }
  218. // 没有appbar并且有tabbar,则给第一个元素加边距
  219. if (!_isHasAppbar && _isHasTabBar) {
  220. result.insert(0, SizedBox(height: MediaQueryData.fromWindow(window).padding.top));
  221. }
  222. return result;
  223. }
  224. /// appbar
  225. Widget _buildAppbar(final Map<String, dynamic> model) {
  226. if (EmptyUtil.isEmpty(model)) return null;
  227. String mobName = model['mod_name'];
  228. if (!mobName.contains('_appbar')) return null;
  229. Map<String, dynamic> data = Map<String, dynamic>();
  230. try {
  231. data = jsonDecode(model['data']);
  232. } catch (e, s) {
  233. Logger.warn(e, s);
  234. }
  235. String parentTitle = !EmptyUtil.isEmpty(widget?.data) ? widget?.data['title_1'] ?? '' : '';
  236. _isHasAppbar = true;
  237. return AppBar(
  238. backgroundColor: HexColor.fromHex(null != data ? data['app_bar_bg_color'] ?? '#FFFFFF' : '#FFFFFF'),
  239. brightness: Brightness.light,
  240. leading: IconButton(
  241. icon: Icon(
  242. Icons.arrow_back_ios,
  243. size: 22,
  244. color: HexColor.fromHex('#333333'),
  245. ),
  246. onPressed: () => Navigator.maybePop(context),
  247. ),
  248. title: Text(
  249. null != data && data.containsKey('app_bar_name') ? data['app_bar_name'] != '自定义页面' ? data['app_bar_name'] : parentTitle : parentTitle,
  250. style: TextStyle(
  251. color: HexColor.fromHex(null != data ? data['app_bar_name_color'] ?? '#333333' : '#333333'),
  252. fontSize: 16,
  253. fontWeight: FontWeight.bold,
  254. ),
  255. ),
  256. centerTitle: true,
  257. elevation: 0,
  258. );
  259. }
  260. /// tabBar
  261. List<Widget> _buildTabBar(final Map<String, dynamic> model, final int index) {
  262. Map<String, dynamic> data = Map<String, dynamic>();
  263. List<Map<String, dynamic>> listStyle = [];
  264. List<Widget> result = [];
  265. try {
  266. data = jsonDecode(model['data']);
  267. listStyle = List.from(data['list_style']);
  268. } catch (e, s) {
  269. Logger.warn(e, s);
  270. }
  271. // 1、导航栏没开启的情况 传null进去进行获取没开启导航栏的widget集合
  272. if (EmptyUtil.isEmpty(listStyle)) {
  273. result.add(Expanded(
  274. child: CustomItemPage(null, 0, model['mod_id']?.toString() ?? null, model['mod_pid']?.toString() ?? null, (!_isHasAppbar && index == 0 )),
  275. ));
  276. return result;
  277. }
  278. // 2、导航栏开启的情况
  279. if (listStyle.length > 0) {
  280. // tabContorller 初始化
  281. if (null == _tabController || _tabController.length != listStyle.length) {
  282. _tabController = new TabController(length: listStyle.length, vsync: this);
  283. }
  284. result.add(Container(
  285. height: 40,
  286. width: double.infinity,
  287. // color: HexColor.fromHex('#FFFFFF'),
  288. child: TabBar(
  289. controller: _tabController,
  290. isScrollable: /*listStyle.length <= 5 ? false : */ true,
  291. labelColor: HexColor.fromHex('#FF4242'),
  292. labelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
  293. unselectedLabelColor: HexColor.fromHex('#999999'),
  294. unselectedLabelStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
  295. indicatorSize: TabBarIndicatorSize.label,
  296. indicator: MaterialIndicator(
  297. color: HexColor.fromHex('#FF4242'),
  298. bottomLeftRadius: 1.25,
  299. topLeftRadius: 1.25,
  300. topRightRadius: 1.25,
  301. bottomRightRadius: 1.25,
  302. height: 2.5,
  303. horizontalPadding: 5,
  304. ),
  305. tabs: listStyle.map((e) => Text(e['name'])).toList(),
  306. ),
  307. ));
  308. _isHasTabBar = true;
  309. // 最后添加TabBarView
  310. result.add(Expanded(
  311. child: TabBarView(
  312. controller: _tabController,
  313. children: _buildTabBarViewChildren(listStyle, model['mod_id']?.toString(), model['mod_pid']?.toString(), index),
  314. ),
  315. ));
  316. }
  317. return result;
  318. }
  319. /// 返回TabBarView的视图
  320. List<Widget> _buildTabBarViewChildren(final List<Map<String, dynamic>> listStyle, final String modId, final String modPid, final int index) {
  321. List<Widget> result = [];
  322. for (int i = 0; i < listStyle.length; i++) {
  323. result.add(CustomItemPage(listStyle[i], i, modId, modPid, (!_isHasAppbar && !_isHasTabBar && index == 0 )));
  324. }
  325. return result;
  326. }
  327. // /// 悬浮按钮
  328. // Widget _buildFloatWidget() {
  329. // return Visibility(
  330. // visible: true,
  331. // child: GestureDetector(
  332. // onTap: () => _scrollTop(),
  333. // behavior: HitTestBehavior.opaque,
  334. // child: Container(
  335. // height: 30,
  336. // width: 30,
  337. // child: Icon(Icons.arrow_upward),
  338. // ),
  339. // ),
  340. // );
  341. // }
  342. }
  343. /// 回到顶部的icon
  344. class _CustomFloatingActionButtonLocation extends FloatingActionButtonLocation {
  345. FloatingActionButtonLocation location;
  346. double offsetX; // X方向的偏移量
  347. double offsetY; // Y方向的偏移量
  348. _CustomFloatingActionButtonLocation(this.location, this.offsetX, this.offsetY);
  349. @override
  350. Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) {
  351. Offset offset = location.getOffset(scaffoldGeometry);
  352. return Offset(offset.dx + offsetX, offset.dy + offsetY);
  353. }
  354. }