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 | ... | ... |