From 0a4f6b2508ca5e4c66de07dc353a1b4a01a8ba62 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 26 May 2023 21:40:16 +0800 Subject: [PATCH] =?UTF-8?q?mod:=20=E5=9B=BE=E7=89=87=E9=A2=84=E8=A7=88UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/preview/view.dart | 305 ++++++++++++++++++++---------------- lib/router/app_pages.dart | 8 +- pubspec.lock | 8 + pubspec.yaml | 1 + 4 files changed, 184 insertions(+), 138 deletions(-) diff --git a/lib/pages/preview/view.dart b/lib/pages/preview/view.dart index 2ff9bd73..dfcd7b07 100644 --- a/lib/pages/preview/view.dart +++ b/lib/pages/preview/view.dart @@ -1,3 +1,4 @@ +import 'package:dismissible_page/dismissible_page.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:extended_image/extended_image.dart'; @@ -16,24 +17,25 @@ class ImagePreview extends StatefulWidget { class _ImagePreviewState extends State with TickerProviderStateMixin { final PreviewController _previewController = Get.put(PreviewController()); - late AnimationController animationController; + // late AnimationController animationController; late AnimationController _doubleClickAnimationController; Animation? _doubleClickAnimation; late DoubleClickAnimationListener _doubleClickAnimationListener; List doubleTapScales = [1.0, 2.0]; + bool _dismissDisabled = false; @override void initState() { super.initState(); - animationController = AnimationController( - vsync: this, duration: const Duration(milliseconds: 400)); + // animationController = AnimationController( + // vsync: this, duration: const Duration(milliseconds: 400)); _doubleClickAnimationController = AnimationController( duration: const Duration(milliseconds: 250), vsync: this); } @override void dispose() { - animationController.dispose(); + // animationController.dispose(); _doubleClickAnimationController.dispose(); clearGestureDetailsCache(); super.dispose(); @@ -41,143 +43,172 @@ class _ImagePreviewState extends State @override Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - appBar: AppBarWidget( - controller: animationController, - visible: _previewController.visiable, - child: AppBar( - backgroundColor: Theme.of(context).colorScheme.background, - elevation: 0, - centerTitle: false, - title: Obx( - () => Text.rich( - TextSpan(children: [ - TextSpan(text: _previewController.currentPage.toString()), - const TextSpan(text: ' / '), - TextSpan(text: _previewController.imgList.length.toString()), - ]), - ), - ), - actions: [ - PopupMenuButton( - icon: const Icon(Icons.more_vert), - tooltip: 'action', - itemBuilder: (BuildContext context) => [ - PopupMenuItem( - value: 'share', - onTap: _previewController.onShareImg, - child: const Text('分享'), - ), - PopupMenuItem( - value: 'save', - onTap: _previewController.onSaveImg, - child: const Text('保存'), - ), - ], - ), - ], - ), - ), - body: GestureDetector( - onTap: () { - _previewController.visiable = !_previewController.visiable; - setState(() {}); - }, - child: ExtendedImageGesturePageView.builder( - controller: ExtendedPageController( - initialPage: _previewController.initialPage.value, - pageSpacing: 0, - ), - onPageChanged: (int index) { - _previewController.initialPage.value = index; - _previewController.currentPage.value = index + 1; + return Stack( + children: [ + DismissiblePage( + backgroundColor: Colors.transparent, + onDismissed: () { + Navigator.of(context).pop(); }, - canScrollPage: (GestureDetails? gestureDetails) => - gestureDetails!.totalScale! <= 1.0, - preloadPagesCount: 2, - itemCount: _previewController.imgList.length, - itemBuilder: (BuildContext context, int index) { - return ExtendedImage.network( - _previewController.imgList[index], - fit: BoxFit.contain, - mode: ExtendedImageMode.gesture, - onDoubleTap: (ExtendedImageGestureState state) { - final Offset? pointerDownPosition = state.pointerDownPosition; - final double? begin = state.gestureDetails!.totalScale; - double end; - - //remove old - _doubleClickAnimation - ?.removeListener(_doubleClickAnimationListener); - - //stop pre - _doubleClickAnimationController.stop(); - - //reset to use - _doubleClickAnimationController.reset(); - - if (begin == doubleTapScales[0]) { - end = doubleTapScales[1]; - } else { - end = doubleTapScales[0]; - } - - _doubleClickAnimationListener = () { - state.handleDoubleTap( - scale: _doubleClickAnimation!.value, - doubleTapPosition: pointerDownPosition); - }; - _doubleClickAnimation = _doubleClickAnimationController - .drive(Tween(begin: begin, end: end)); - - _doubleClickAnimation! - .addListener(_doubleClickAnimationListener); - - _doubleClickAnimationController.forward(); + // Note that scrollable widget inside DismissiblePage might limit the functionality + // If scroll direction matches DismissiblePage direction + direction: DismissiblePageDismissDirection.down, + disabled: _dismissDisabled, + isFullScreen: true, + child: Hero( + tag: _previewController + .imgList[_previewController.initialPage.value], + child: GestureDetector( + onTap: () { + _previewController.visiable = !_previewController.visiable; + setState(() {}); }, - loadStateChanged: (ExtendedImageState state) { - if (state.extendedImageLoadState == LoadState.loading) { - final ImageChunkEvent? loadingProgress = - state.loadingProgress; - final double? progress = - loadingProgress?.expectedTotalBytes != null - ? loadingProgress!.cumulativeBytesLoaded / - loadingProgress.expectedTotalBytes! - : null; - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: 150.0, - child: LinearProgressIndicator(value: progress), - ), - const SizedBox(height: 10.0), - Text('${((progress ?? 0.0) * 100).toInt()}%'), - ], - ), + child: ExtendedImageGesturePageView.builder( + controller: ExtendedPageController( + initialPage: _previewController.initialPage.value, + pageSpacing: 0, + ), + onPageChanged: (int index) { + _previewController.initialPage.value = index; + _previewController.currentPage.value = index + 1; + }, + canScrollPage: (GestureDetails? gestureDetails) => + gestureDetails!.totalScale! <= 1.0, + preloadPagesCount: 2, + itemCount: _previewController.imgList.length, + itemBuilder: (BuildContext context, int index) { + return ExtendedImage.network( + _previewController.imgList[index], + fit: BoxFit.contain, + mode: ExtendedImageMode.gesture, + onDoubleTap: (ExtendedImageGestureState state) { + final Offset? pointerDownPosition = + state.pointerDownPosition; + final double? begin = state.gestureDetails!.totalScale; + double end; + + //remove old + _doubleClickAnimation + ?.removeListener(_doubleClickAnimationListener); + + //stop pre + _doubleClickAnimationController.stop(); + + //reset to use + _doubleClickAnimationController.reset(); + + if (begin == doubleTapScales[0]) { + setState(() { + _dismissDisabled = true; + }); + end = doubleTapScales[1]; + } else { + setState(() { + _dismissDisabled = false; + }); + end = doubleTapScales[0]; + } + + _doubleClickAnimationListener = () { + state.handleDoubleTap( + scale: _doubleClickAnimation!.value, + doubleTapPosition: pointerDownPosition); + }; + _doubleClickAnimation = _doubleClickAnimationController + .drive(Tween(begin: begin, end: end)); + + _doubleClickAnimation! + .addListener(_doubleClickAnimationListener); + + _doubleClickAnimationController.forward(); + }, + loadStateChanged: (ExtendedImageState state) { + if (state.extendedImageLoadState == LoadState.loading) { + final ImageChunkEvent? loadingProgress = + state.loadingProgress; + final double? progress = + loadingProgress?.expectedTotalBytes != null + ? loadingProgress!.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null; + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: 150.0, + child: LinearProgressIndicator(value: progress), + ), + const SizedBox(height: 10.0), + Text('${((progress ?? 0.0) * 100).toInt()}%'), + ], + ), + ); + } + }, + initGestureConfigHandler: (ExtendedImageState state) { + return GestureConfig( + inPageView: true, + initialScale: 1.0, + maxScale: 5.0, + animationMaxScale: 6.0, + initialAlignment: InitialAlignment.center, + ); + }, ); - } - }, - initGestureConfigHandler: (ExtendedImageState state) { - return GestureConfig( - inPageView: true, - initialScale: 1.0, - maxScale: 5.0, - animationMaxScale: 6.0, - initialAlignment: InitialAlignment.center, - ); - }, - ); - }, + }, + ), + ), + ), ), - ), - floatingActionButton: FloatingActionButton( - onPressed: () => _previewController.onSaveImg(), - child: const Icon(Icons.save_alt_rounded), - ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + // height: 45, + padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 10, top: 20), + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black87, + ], + tileMode: TileMode.mirror, + ), + ), + child: Padding( + padding: const EdgeInsets.only(left: 20, right: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Obx( + () => Text.rich( + TextSpan( + style: const TextStyle( + color: Colors.white, + fontSize: 18 + ), + children: [ + TextSpan(text: _previewController.currentPage.toString()), + const TextSpan(text: ' / '), + TextSpan(text: _previewController.imgList.length.toString()), + ]), + ), + ), + FloatingActionButton( + onPressed: () => _previewController.onSaveImg(), + child: const Icon(Icons.save_alt_rounded), + ), + ], + ), + ), + ), + ), + ], ); } } diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index e8030d96..0edbc14f 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -19,7 +19,13 @@ class Routes { // 视频详情 GetPage(name: '/video', page: () => const VideoDetailPage()), // 图片预览 - GetPage(name: '/preview', page: () => const ImagePreview()), + GetPage( + name: '/preview', + page: () => const ImagePreview(), + transition: Transition.fade, + transitionDuration: const Duration(milliseconds: 300), + showCupertinoParallax: false, + ), // GetPage(name: '/webview', page: () => const WebviewPage()), // 设置 diff --git a/pubspec.lock b/pubspec.lock index 3fc4304b..058324e3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -281,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + dismissible_page: + dependency: "direct main" + description: + name: dismissible_page + sha256: "5b2316f770fe83583f770df1f6505cb19102081c5971979806e77f2e507a9958" + url: "https://pub.dev" + source: hosted + version: "1.0.2" dynamic_color: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ac13cef2..195cde86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,7 @@ dependencies: font_awesome_flutter: ^10.4.0 # toast flutter_smart_dialog: ^4.9.0+6 + dismissible_page: ^1.0.2 dev_dependencies: flutter_test: