fluttercandies / extended_image

A powerful official extension library of image, which support placeholder(loading)/ failed state, cache network, zoom pan image, photo view, slide out page, editor(crop,rotate,flip), paint custom etc.
https://fluttercandies.github.io/extended_image/
MIT License
1.93k stars 504 forks source link

[Bug report] ExtendedImageSlidePage中嵌套图片或者ExtendedImageGesturePageView的问题 #683

Open wangzhipeng-vicky opened 5 months ago

wangzhipeng-vicky commented 5 months ago

Version

8.2.0

Platforms

Android, iOS

Device Model

小米9

flutter info

Flutter version 2.5.1

How to reproduce?

我是ExtendedImageSlidePage中嵌套ExtendedImageGesturePageView,实现的看大图的需求,就是我手往下滑动大图界面能关掉,没有问题。但是问题是:

image image

`@override Widget build(BuildContext context) { final Size size = MediaQuery.of(context).size; imageDRect = Offset.zero & size;

Widget result = ExtendedImageSlidePage(
    key: slidePagekey,
    // slideAxis: SlideAxis.vertical,
    // slideType: SlideType.wholePage,

    //在滑动页面的时候根据 Offset 自定义整个页面的背景色
    slidePageBackgroundHandler: (
      Offset offset,
      Size pageSize,
    ) {
      double opacity = 0.0;
      // if (pageGestureAxis == SlideAxis.both) {
      opacity = offset.distance /
          (Offset(pageSize.width, pageSize.height).distance / 2.0);
      // } else if (pageGestureAxis == SlideAxis.horizontal) {
      //   opacity = offset.dx.abs() / (pageSize.width / 2.0);
      // } else if (pageGestureAxis == SlideAxis.vertical) {
      //   opacity = offset.dy.abs() / (pageSize.height / 2.0);
      // }
      return const Color(0x00000000)
          .withOpacity(min(1.0, max(1.0 - opacity, 0.0)));
    },

    //在滑动页面的时候根据 Offset 自定义整个页面的缩放值
    slideScaleHandler: (
      Offset offset, {
      ExtendedImageSlidePageState? state,
    }) {
      //image is ready and it's not sliding.
      if (state != null &&
          detailKeys[_currentIndex!] != null &&
          state.scale == 1.0) {
        //don't slide page if scale of image is more than 1.0
        if (state.imageGestureState!.gestureDetails!.totalScale! > 1.0) {
          return 1.0;
        }
        //or slide down into detail mode
        if (offset.dy < 0 || _imageDetailY < 0) {
          return 1.0;
        }
      }

      return null;
    },

    //在滑动页面的时候自定义 Offset
    slideOffsetHandler: (
      Offset offset, {
      ExtendedImageSlidePageState? state,
    }) {

      //image is ready and it's not sliding.
      if (state != null &&
          detailKeys[_currentIndex!] != null &&
          state.scale == 1.0) {
        //don't slide page if scale of image is more than 1.0

        if (state.imageGestureState!.gestureDetails!.totalScale! > 1.0) {
          return Offset.zero;
        }

        //or slide down into detail mode
        if (offset.dy < 0 || _imageDetailY < 0) {
          _imageDetailY += offset.dy;

          // print(offset.dy);
          _imageDetailY = max(
              -detailKeys[_currentIndex!]!.maxImageDetailY, _imageDetailY);
          rebuildDetail.sink.add(_imageDetailY);
          return Offset.zero;
        }

        if (_imageDetailY != 0) {
          _imageDetailY = 0;
          _showSwiper = true;
          rebuildSwiper.add(_showSwiper);
          rebuildDetail.sink.add(_imageDetailY);
        }
      }
      return null;
    },

    //滑动页面结束的时候计算是否需要 pop 页面
    slideEndHandler: (
      Offset offset, {
      ExtendedImageSlidePageState? state,
      ScaleEndDetails? details,
    }) {

      if (_imageDetailY != 0 && state!.scale == 1) {
        if (!_slideEndAnimationController.isAnimating) {

// get magnitude from gesture velocity final double magnitude = details!.velocity.pixelsPerSecond.distance;

          // do a significant magnitude

          if (magnitude.greaterThanOrEqualTo(minMagnitude)) {
            final Offset direction =
                details.velocity.pixelsPerSecond / magnitude * 1000;

            _slideEndAnimation =
                _slideEndAnimationController.drive(Tween<double>(
              begin: _imageDetailY,
              end: (_imageDetailY + direction.dy)
                  .clamp(-detailKeys[_currentIndex!]!.maxImageDetailY, 0.0),
            ));
            _slideEndAnimationController.reset();
            _slideEndAnimationController.forward();
          }
        }
        return false;
      }

      return null;
    },

    //滑动页面的回调,你可以在这里改变页面上其他元素的状态
    onSlidingPage: (ExtendedImageSlidePageState state) {

      ///you can change other widgets' state on page as you want
      ///base on offset/isSliding etc
      //var offset= state.offset;
      final bool showSwiper = !state.isSliding;
      if (showSwiper != _showSwiper) {
        // do not setState directly here, the image state will change,
        // you should only notify the widgets which are needed to change
        // setState(() {
        // _showSwiper = showSwiper;
        // });

        _showSwiper = showSwiper;
        rebuildSwiper.add(_showSwiper);
      }
    },

    child: Material(
      /// if you use ExtendedImageSlidePage and slideType =SlideType.onlyImage,
      /// make sure your page is transparent background
      color: Colors.transparent,
      // color: Colors.redAccent,

      shadowColor: Colors.transparent,

      child: Stack(
        fit: StackFit.expand,
        children: <Widget>[
          ExtendedImageGesturePageView.builder(
            controller: ExtendedPageController(
              initialPage: widget.index!,
              // pageSpacing: 32,//两个图之间的边距  越大,在A图的右边就能看见B图  2024年5月31日11:18:28
              shouldIgnorePointerWhenScrolling: true,
            ),
            scrollDirection: Axis.horizontal,
            // physics: const BouncingScrollPhysics(),
            //定义PageView的滚动行为
            canScrollPage: (GestureDetails? gestureDetails) {
              return _imageDetailY >= 0;
              //return (gestureDetails?.totalScale ?? 1.0) <= 1.0;
            },
            itemBuilder: (BuildContext context, int index) {
              int imageTyep = widget.pics![index].type;

              late Widget image;

              Map extendParams = {
                "fit": BoxFit.contain,
                "enableSlideOutPage": true,
                "mode": ExtendedImageMode.gesture,
                // 手势配置的回调(图片加载完成时).你可以根据图片的信息比如宽高,来初始化
                "initGestureConfigHandler": (ExtendedImageState state) {
                  double? initialScale = 1.0;

                  //暂时不要这个了   直接初始默认就是1  而不要计算的,因为对于大图会出现一开始是方法的模式 2024年5月31日10:26:11
                  if (state.extendedImageInfo != null) {
                    initialScale = initScale(
                        size: size,
                        initialScale: initialScale,
                        imageSize: Size(
                            state.extendedImageInfo!.image.width.toDouble(),
                            state.extendedImageInfo!.image.height
                                .toDouble()));
                  }
                  return GestureConfig(
                    inPageView: true,
                    // initialScale: initialScale!,
                    initialScale: 1,
                    // maxScale: max(initialScale, 5.0),
                    // animationMaxScale: max(initialScale, 5.0),
                    initialAlignment: InitialAlignment.center,
                    minScale: 1.0,
                    //you can cache gesture state even though page view page change.
                    //remember call clearGestureDetailsCache() method at the right time.(for example,this page dispose)
                    cacheGesture: true,
                  );
                },

                "onDoubleTap": (ExtendedImageGestureState state) {
                  ///you can use define pointerDownPosition as you can,
                  ///default value is double tap pointer down postion.
                  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 = () {
                    //print(_animation.value);
                    state.handleDoubleTap(
                        scale: _doubleClickAnimation!.value,
                        doubleTapPosition: pointerDownPosition);
                  };
                  _doubleClickAnimation = _doubleClickAnimationController
                      .drive(Tween<double>(begin: begin, end: end));

                  _doubleClickAnimation!
                      .addListener(_doubleClickAnimationListener);

                  _doubleClickAnimationController.forward();
                },
              };

              if (imageTyep == 0 || imageTyep == 60) {
                //网络图

                final String? originPath = widget.pics![index].originPath;
                String? demoPath = widget.pics![index].demoPath;
                String? prePath = widget.pics![index].preViewDemo;
                String item;
                if (imageTyep == 60) {
                  item = prePath ?? demoPath!;
                } else {
                  item = originPath!;
                }

                image = createNetworkImage(
                  item,
                  index,
                  extendParams: extendParams,
                  // fit: BoxFit.contain,
                  // enableSlideOutPage: true,
                  // mode: ExtendedImageMode.gesture,

                  loadStateChanged: (ExtendedImageState state) {
                    if (state.extendedImageLoadState ==
                        LoadState.completed) {
                      final Rect imageDRect = getDestinationRect(
                        rect: Offset.zero & size,
                        inputSize: Size(
                          state.extendedImageInfo!.image.width.toDouble(),
                          state.extendedImageInfo!.image.height.toDouble(),
                        ),
                        fit: BoxFit.contain,
                      );

                      detailKeys[index] ??= ImageDetailInfo(
                        imageDRect: imageDRect,
                        pageSize: size,
                        imageInfo: state.extendedImageInfo!,
                      );

                      final ImageDetailInfo? imageDetailInfo =
                          detailKeys[index];

                      return StreamBuilder<double>(
                        builder: (BuildContext context,
                            AsyncSnapshot<double> data) {
                          return ExtendedImageGesture(
                            state,
                            canScaleImage: (_) => _imageDetailY == 0,
                            imageBuilder: (Widget image) {
                              return Stack(
                                children: <Widget>[
                                  Positioned.fill(
                                    top: _imageDetailY,
                                    bottom: -_imageDetailY,
                                    child: image,
                                  ),
                                  Positioned(
                                    left: 0.0,
                                    right: 0.0,
                                    top: imageDetailInfo!.imageBottom +
                                        _imageDetailY,
                                    child: Opacity(
                                      opacity: _imageDetailY == 0
                                          ? 0
                                          : min(
                                              1,
                                              _imageDetailY.abs() /
                                                  (imageDetailInfo
                                                          .maxImageDetailY /
                                                      4.0),
                                            ),
                                    ),
                                  ),
                                ],
                              );
                            },
                          );
                        },
                        initialData: _imageDetailY,
                        stream: rebuildDetail.stream,
                      );
                    } else if (state.extendedImageLoadState ==
                        LoadState.loading) {
                      return Center(
                        child: Image.asset(
                          "assets/images/loading.gif",
                          width: 40,
                          height: 40,
                        ),
                      );
                    } else if (state.extendedImageLoadState ==
                        LoadState.failed) {
                      return GestureDetector(
                        child: /*Container(
                color: const Color(0xFFF3F4F5),
                child:*/
                            Column(
                                mainAxisAlignment: MainAxisAlignment.center,
                                crossAxisAlignment:
                                    CrossAxisAlignment.center,
                                children: [
                              Image.asset(
                                "assets/images/img_load_failed.png",
                                // "assets/images/loading.gif",
                                width: 40,
                                height: 40,
                              ),
                              const Padding(
                                padding: EdgeInsets.only(
                                    left: 3, top: 15, right: 3),
                                child: Text(
                                  "加载失败,点击重试",
                                  style: TextStyle(
                                      fontSize: 15,
                                      color: Color(0XFF999999)),
                                  textAlign: TextAlign.center,
                                ),
                              )
                            ]),
                        // ),
                        onTap: () {
                          state.reLoadImage();
                        },
                      );
                    }
                    return null;
                  },
                );
              } else if (imageTyep == 1 || imageTyep == 61) {
                image = createFileWithWechatPicker(
                  widget.pics![index].entity!,
                  extendParams: extendParams,
                );

                // tag = widget.pics![index].tag;
              }

              if (index < min(9, widget.pics!.length)) {
                image = HeroWidget(
                  tag: widget.pics![index].tag,
                  slideType: SlideType.onlyImage,
                  slidePagekey: slidePagekey,
                  child: image,
                );
              }

              image = GestureDetector(
                child: image,
                onTap: () {
                  if (_imageDetailY != 0) {
                    _imageDetailY = 0;
                    rebuildDetail.sink.add(_imageDetailY);
                  } else {
                    slidePagekey.currentState!.popPage();
                    Navigator.pop(context);
                  }
                },
              );

              return  image;
            },

            itemCount: widget.pics!.length,

            onPageChanged: (int index) {
              _currentIndex = index;
              rebuildIndex.add(index);
              if (_imageDetailY != 0) {
                _imageDetailY = 0;
                rebuildDetail.sink.add(_imageDetailY);
              }
              _showSwiper = true;
              rebuildSwiper.add(_showSwiper);
              _preloadImage(index - 1);
              _preloadImage(index + 1);
            },
          ),
          Visibility(
              visible: !(widget.isOnlyPreview && widget.pics!.length == 1),
              child: StreamBuilder<bool>(
                //负责根据不同状态创建对应ui的方法实现
                builder: (BuildContext c, AsyncSnapshot<bool> d) {
                  if (d.data == null || !d.data!) {
                    return Container();
                  }

                  return Positioned(
                    bottom: 0.0,
                    left: 0.0,
                    right: 0.0,
                    child: MySwiperPlugin(widget.pics, _currentIndex,
                        rebuildIndex, widget.operateMenu),
                  );
                },
                initialData: true, //默认初始化数据
                stream: rebuildSwiper
                    .stream, //stream事件流对象   rebuildSwiper.stream获取Stream用作事件监听
              ))
        ],
      ),
    ));
return  result;

}`

Logs

No response

Example code (optional)

No response

Contact

623301600@qq.com