Commit 3ba925a9d0b3386434fe89c90552b3482a056dd5
1 parent
27dad3d1
feat:环节页增加翻页切换单元效果
Showing
6 changed files
with
176 additions
and
98 deletions
lib/pages/section/bloc/section_bloc.dart
... | ... | @@ -10,12 +10,26 @@ import 'package:wow_english/utils/toast_util.dart'; |
10 | 10 | |
11 | 11 | import '../../../models/course_section_entity.dart'; |
12 | 12 | import '../../../models/course_unit_entity.dart'; |
13 | +import '../../../utils/list_ext.dart'; | |
13 | 14 | |
14 | 15 | part 'section_event.dart'; |
15 | 16 | part 'section_state.dart'; |
16 | 17 | |
17 | 18 | class SectionBloc extends Bloc<SectionEvent, SectionState> { |
18 | 19 | |
20 | + PageController _pageController; | |
21 | + | |
22 | + PageController get pageController => _pageController; | |
23 | + | |
24 | + ///当前页索引 | |
25 | + int _currentPage = 0; | |
26 | + | |
27 | + int get currentPage => _currentPage; | |
28 | + | |
29 | + ScrollController _listController; | |
30 | + | |
31 | + ScrollController get listController => _listController; | |
32 | + | |
19 | 33 | CourseUnitEntity _courseUnitEntity; |
20 | 34 | |
21 | 35 | CourseUnitEntity get courseUnitEntity => _courseUnitEntity; |
... | ... | @@ -32,11 +46,12 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
32 | 46 | |
33 | 47 | CourseProcessEntity? get processEntity => _processEntity; |
34 | 48 | |
35 | - SectionBloc(this._courseUnitEntity, this._courseUnitDetail) : super(LessonInitial()) { | |
49 | + SectionBloc(this._courseUnitEntity, this._courseUnitDetail, this._pageController, this._listController) : super(LessonInitial()) { | |
36 | 50 | on<RequestDataEvent>(_requestData); |
37 | 51 | on<RequestExitClassEvent>(_requestExitClass); |
38 | 52 | on<RequestEnterClassEvent>(_requestEnterClass); |
39 | 53 | on<RequestVideoLessonEvent>(_requestVideoLesson); |
54 | + on<CurrentUnitIndexChangeEvent>(_pageControllerChange); | |
40 | 55 | } |
41 | 56 | |
42 | 57 | void _requestData(RequestDataEvent event, Emitter<SectionState> emitter) async { |
... | ... | @@ -82,4 +97,15 @@ class SectionBloc extends Bloc<SectionEvent, SectionState> { |
82 | 97 | void _requestExitClass(RequestExitClassEvent event,Emitter<SectionState> emitter) async { |
83 | 98 | await ListenDao.exitClass(event.courseLessonId,event.currentStep,event.currentTime); |
84 | 99 | } |
100 | + | |
101 | + void _pageControllerChange(CurrentUnitIndexChangeEvent event, | |
102 | + Emitter<SectionState> emitter) async { | |
103 | + _currentPage = event.unitIndex; | |
104 | + emitter(CurrentPageIndexState()); | |
105 | + } | |
106 | + | |
107 | + // 未锁定的页数 | |
108 | + int unlockPageCount() { | |
109 | + return _courseUnitEntity.courseUnitVOList?.indexWhereOrNull((element) => element.lock == true) ?? 1; | |
110 | + } | |
85 | 111 | } | ... | ... |
lib/pages/section/bloc/section_event.dart
... | ... | @@ -26,3 +26,9 @@ class RequestExitClassEvent extends SectionEvent { |
26 | 26 | final String currentTime; |
27 | 27 | RequestExitClassEvent(this.courseLessonId,this.currentStep,this.currentTime); |
28 | 28 | } |
29 | + | |
30 | +///页面切换 | |
31 | +class CurrentUnitIndexChangeEvent extends SectionEvent { | |
32 | + final int unitIndex; | |
33 | + CurrentUnitIndexChangeEvent(this.unitIndex); | |
34 | +} | ... | ... |
lib/pages/section/bloc/section_state.dart
... | ... | @@ -10,11 +10,15 @@ class LessonDataLoadState extends SectionState {} |
10 | 10 | class RequestVideoLessonState extends SectionState { |
11 | 11 | final String courseLessonId; |
12 | 12 | final int type; |
13 | - RequestVideoLessonState(this.courseLessonId,this.type); | |
13 | + | |
14 | + RequestVideoLessonState(this.courseLessonId, this.type); | |
14 | 15 | } |
15 | 16 | |
16 | -class RequestEnterClassState extends SectionState{ | |
17 | +class RequestEnterClassState extends SectionState { | |
17 | 18 | final String courseLessonId; |
18 | 19 | final int courseType; |
19 | - RequestEnterClassState(this.courseLessonId,this.courseType); | |
20 | + | |
21 | + RequestEnterClassState(this.courseLessonId, this.courseType); | |
20 | 22 | } |
23 | + | |
24 | +class CurrentPageIndexState extends SectionState {} | ... | ... |
lib/pages/section/section_page.dart
1 | +import 'package:flutter/cupertino.dart'; | |
1 | 2 | import 'package:flutter/material.dart'; |
2 | 3 | import 'package:flutter_bloc/flutter_bloc.dart'; |
3 | 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; |
5 | +import 'package:nested_scroll_views/material.dart'; | |
4 | 6 | import 'package:wow_english/common/core/user_util.dart'; |
5 | 7 | import 'package:wow_english/common/extension/string_extension.dart'; |
6 | 8 | import 'package:wow_english/models/course_unit_entity.dart'; |
... | ... | @@ -16,7 +18,10 @@ import 'courese_module_model.dart'; |
16 | 18 | |
17 | 19 | /// 环节列表页 |
18 | 20 | class SectionPage extends StatelessWidget { |
19 | - const SectionPage({super.key, required this.courseUnitEntity, required this.courseUnitDetail}); | |
21 | + const SectionPage( | |
22 | + {super.key, | |
23 | + required this.courseUnitEntity, | |
24 | + required this.courseUnitDetail}); | |
20 | 25 | |
21 | 26 | final CourseUnitEntity courseUnitEntity; |
22 | 27 | |
... | ... | @@ -26,7 +31,9 @@ class SectionPage extends StatelessWidget { |
26 | 31 | @override |
27 | 32 | Widget build(BuildContext context) { |
28 | 33 | return BlocProvider( |
29 | - create: (context) => SectionBloc(courseUnitEntity, courseUnitDetail)..add(RequestDataEvent()), | |
34 | + create: (context) => SectionBloc(courseUnitEntity, courseUnitDetail, | |
35 | + PageController(), ScrollController()) | |
36 | + ..add(RequestDataEvent()), | |
30 | 37 | child: _SectionPageView(context), |
31 | 38 | ); |
32 | 39 | } |
... | ... | @@ -115,11 +122,11 @@ class _SectionPageView extends StatelessWidget { |
115 | 122 | } |
116 | 123 | } |
117 | 124 | }, |
118 | - child: _homeView(), | |
125 | + child: _sectionView(), | |
119 | 126 | ); |
120 | 127 | } |
121 | 128 | |
122 | - Widget _homeView() => | |
129 | + Widget _sectionView() => | |
123 | 130 | BlocBuilder<SectionBloc, SectionState>(builder: (context, state) { |
124 | 131 | final bloc = BlocProvider.of<SectionBloc>(context); |
125 | 132 | return Scaffold( |
... | ... | @@ -133,96 +140,65 @@ class _SectionPageView extends StatelessWidget { |
133 | 140 | title: bloc.courseUnitDetail.name, |
134 | 141 | courseModuleCode: bloc.courseUnitEntity.courseModuleCode), |
135 | 142 | Expanded( |
136 | - child: ListView.builder( | |
137 | - itemCount: bloc.courseSectionDatas?.length ?? 0, | |
138 | - scrollDirection: Axis.horizontal, | |
139 | - itemBuilder: (BuildContext context, int index) { | |
140 | - CourseSectionEntity sectionData = | |
141 | - bloc.courseSectionDatas![index]; | |
142 | - if (sectionData.courseType == 5) { | |
143 | - //彩蛋 | |
144 | - return GestureDetector( | |
145 | - onTap: () { | |
146 | - if (!UserUtil.isLogined()) { | |
147 | - pushNamed(AppRouteName.login); | |
148 | - return; | |
149 | - } | |
150 | - if (sectionData.lock == true) { | |
151 | - showToast('当前课程暂未解锁'); | |
152 | - return; | |
153 | - } | |
154 | - | |
155 | - ///进入课堂 | |
156 | - bloc.add(RequestEnterClassEvent( | |
157 | - sectionData.id.toString(), sectionData.courseType)); | |
158 | - }, | |
159 | - child: SectionBoundsItem( | |
160 | - imageUrl: sectionData.coverUrl, | |
161 | - ), | |
162 | - ); | |
163 | - } else { | |
164 | - return GestureDetector( | |
165 | - onTap: () { | |
166 | - if (!UserUtil.isLogined()) { | |
167 | - pushNamed(AppRouteName.login); | |
168 | - return; | |
169 | - } | |
170 | - if (sectionData.lock == true) { | |
171 | - showToast('当前课程暂未解锁'); | |
172 | - return; | |
173 | - } | |
174 | - | |
175 | - ///进入课堂 | |
176 | - bloc.add(RequestEnterClassEvent( | |
177 | - sectionData.id.toString(), sectionData.courseType)); | |
178 | - }, | |
179 | - child: SectionVideoItem( | |
180 | - unitEntity: bloc.courseUnitEntity, | |
181 | - lessons: sectionData, | |
182 | - ), | |
183 | - ); | |
184 | - } | |
185 | - })), | |
186 | - // SafeArea( | |
187 | - // child: Padding( | |
188 | - // padding: EdgeInsets.symmetric(horizontal: 13.w), | |
189 | - // child: Row( | |
190 | - // mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
191 | - // children: [ | |
192 | - // SizedBox( | |
193 | - // height: 47.h, | |
194 | - // width: 80.w, | |
195 | - // ), | |
196 | - // Container( | |
197 | - // decoration: BoxDecoration( | |
198 | - // color: CourseModuleModel( | |
199 | - // bloc.courseUnitEntity.courseModuleCode ?? | |
200 | - // 'Phase-1') | |
201 | - // .color, | |
202 | - // borderRadius: BorderRadius.circular(14.5.r), | |
203 | - // ), | |
204 | - // padding: EdgeInsets.symmetric( | |
205 | - // vertical: 8.h, horizontal: 24.w), | |
206 | - // child: Text( | |
207 | - // '${(bloc.courseUnitEntity.nowStep ?? 0)}/${bloc.courseUnitEntity.total ?? 0}', | |
208 | - // style: TextStyle( | |
209 | - // color: Colors.white, fontSize: 12.sp), | |
210 | - // ), | |
211 | - // ), | |
212 | - // Image.asset( | |
213 | - // CourseModuleModel( | |
214 | - // bloc.courseUnitEntity.courseModuleCode ?? | |
215 | - // 'Phase-1') | |
216 | - // .courseModuleLogo | |
217 | - // .assetPng, | |
218 | - // height: 47.h, | |
219 | - // width: 80.w, | |
220 | - // // color: Colors.red, | |
221 | - // ), | |
222 | - // ], | |
223 | - // ), | |
224 | - // ), | |
225 | - // ) | |
143 | + child: Padding( | |
144 | + padding: EdgeInsets.symmetric(horizontal: 10.w), | |
145 | + child: OverflowBox( | |
146 | + child: NestedPageView.builder( | |
147 | + itemCount: bloc.unlockPageCount(), | |
148 | + controller: bloc.pageController, | |
149 | + onPageChanged: (int index) { | |
150 | + bloc.add(CurrentUnitIndexChangeEvent(index)); | |
151 | + }, | |
152 | + itemBuilder: (context, index) { | |
153 | + return ScrollConfiguration( | |
154 | + ///去掉 Android 上默认的边缘拖拽效果 | |
155 | + behavior: ScrollConfiguration.of(context) | |
156 | + .copyWith(overscroll: false), | |
157 | + child: _itemTransCard(index, context), | |
158 | + ); | |
159 | + }), | |
160 | + ), // 设置外部padding, | |
161 | + )), | |
162 | + SafeArea( | |
163 | + child: Padding( | |
164 | + padding: EdgeInsets.symmetric(horizontal: 13.w), | |
165 | + child: Row( | |
166 | + mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
167 | + children: [ | |
168 | + SizedBox( | |
169 | + height: 47.h, | |
170 | + width: 80.w, | |
171 | + ), | |
172 | + Container( | |
173 | + decoration: BoxDecoration( | |
174 | + color: CourseModuleModel( | |
175 | + bloc.courseUnitEntity.courseModuleCode ?? | |
176 | + 'Phase-1') | |
177 | + .color, | |
178 | + borderRadius: BorderRadius.circular(14.5.r), | |
179 | + ), | |
180 | + padding: EdgeInsets.symmetric( | |
181 | + vertical: 8.h, horizontal: 24.w), | |
182 | + child: Text( | |
183 | + '${bloc.currentPage + 1}/${bloc.unlockPageCount()}', | |
184 | + style: TextStyle( | |
185 | + color: Colors.white, fontSize: 12.sp), | |
186 | + ), | |
187 | + ), | |
188 | + Image.asset( | |
189 | + CourseModuleModel( | |
190 | + bloc.courseUnitEntity.courseModuleCode ?? | |
191 | + 'Phase-1') | |
192 | + .courseModuleLogo | |
193 | + .assetPng, | |
194 | + height: 47.h, | |
195 | + width: 80.w, | |
196 | + // color: Colors.red, | |
197 | + ), | |
198 | + ], | |
199 | + ), | |
200 | + ), | |
201 | + ) | |
226 | 202 | ], |
227 | 203 | ), |
228 | 204 | ), |
... | ... | @@ -230,3 +206,56 @@ class _SectionPageView extends StatelessWidget { |
230 | 206 | ); |
231 | 207 | }); |
232 | 208 | } |
209 | + | |
210 | +Widget _itemTransCard(int index, BuildContext context) { | |
211 | + final bloc = BlocProvider.of<SectionBloc>(context); | |
212 | + return NestedListView.builder( | |
213 | + itemCount: bloc.courseSectionDatas?.length ?? 0, | |
214 | + scrollDirection: Axis.horizontal, | |
215 | + itemBuilder: (BuildContext context, int index) { | |
216 | + CourseSectionEntity sectionData = bloc.courseSectionDatas![index]; | |
217 | + if (sectionData.courseType == 5) { | |
218 | + //彩蛋 | |
219 | + return GestureDetector( | |
220 | + onTap: () { | |
221 | + if (!UserUtil.isLogined()) { | |
222 | + pushNamed(AppRouteName.login); | |
223 | + return; | |
224 | + } | |
225 | + if (sectionData.lock == true) { | |
226 | + showToast('当前课程暂未解锁'); | |
227 | + return; | |
228 | + } | |
229 | + | |
230 | + ///进入课堂 | |
231 | + bloc.add(RequestEnterClassEvent( | |
232 | + sectionData.id.toString(), sectionData.courseType)); | |
233 | + }, | |
234 | + child: SectionBoundsItem( | |
235 | + imageUrl: sectionData.coverUrl, | |
236 | + ), | |
237 | + ); | |
238 | + } else { | |
239 | + return GestureDetector( | |
240 | + onTap: () { | |
241 | + if (!UserUtil.isLogined()) { | |
242 | + pushNamed(AppRouteName.login); | |
243 | + return; | |
244 | + } | |
245 | + if (sectionData.lock == true) { | |
246 | + showToast('当前课程暂未解锁'); | |
247 | + return; | |
248 | + } | |
249 | + | |
250 | + ///进入课堂 | |
251 | + bloc.add(RequestEnterClassEvent( | |
252 | + sectionData.id.toString(), sectionData.courseType)); | |
253 | + }, | |
254 | + child: SectionVideoItem( | |
255 | + unitEntity: bloc.courseUnitEntity, | |
256 | + lessons: sectionData, | |
257 | + ), | |
258 | + ); | |
259 | + } | |
260 | + }); | |
261 | +} | ... | ... |
lib/utils/list_ext.dart
0 → 100644
pubspec.yaml
... | ... | @@ -111,6 +111,8 @@ dependencies: |
111 | 111 | umeng_common_sdk: ^1.2.7 |
112 | 112 | # 友盟APM https://pub-web.flutter-io.cn/packages/umeng_apm_sdk |
113 | 113 | umeng_apm_sdk: ^2.2.1 |
114 | + # 嵌套滚动 https://pub.dev/packages/nested_scroll_views | |
115 | + nested_scroll_views: ^0.0.10 | |
114 | 116 | |
115 | 117 | dev_dependencies: |
116 | 118 | build_runner: ^2.4.4 | ... | ... |