• flutter开发实战-hero实现图片预览功能extend_image


    flutter开发实战-hero实现图片预览功能extend_image

    在开发中,经常遇到需要图片预览,当feed中点击一个图片,开启预览,多个图片可以左右切换swiper,双击图片及手势进行缩放功能。
    这个主要实现使用extend_image插件。在点击图片时候使用hero动画进行展示。

    Hero简单使用,可以查看https://brucegwo.blog.csdn.net/article/details/134005601

    hero实现图片预览功能效果图

    在这里插入图片描述
    在这里插入图片描述

    一、图片GridView

    在展示多张图片,使用GridView来展示。

    GridView可以构建一个二维网格列表,其默认构造函数定义如下:

      GridView({
        Key? key,
        Axis scrollDirection = Axis.vertical,
        bool reverse = false,
        ScrollController? controller,
        bool? primary,
        ScrollPhysics? physics,
        bool shrinkWrap = false,
        EdgeInsetsGeometry? padding,
        required this.gridDelegate,  //下面解释
        bool addAutomaticKeepAlives = true,
        bool addRepaintBoundaries = true,
        double? cacheExtent, 
        List<Widget> children = const <Widget>[],
        ...
      })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    SliverGridDelegate是一个抽象类,定义了GridView Layout相关接口,子类需要通过实现它们来实现具体的布局算法。

    实现展示图片GridView

    GridView.builder(
              gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: 300,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
              ),
              itemBuilder: (BuildContext context, int index) {
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    完整代码如下

    class GridSimplePhotoViewDemo extends StatefulWidget {
      
      _GridSimplePhotoViewDemoState createState() =>
          _GridSimplePhotoViewDemoState();
    }
    
    class _GridSimplePhotoViewDemoState extends State<GridSimplePhotoViewDemo> {
      List<String> images = <String>[
        'https://photo.tuchong.com/14649482/f/601672690.jpg',
        'https://photo.tuchong.com/17325605/f/641585173.jpg',
        'https://photo.tuchong.com/3541468/f/256561232.jpg',
        'https://photo.tuchong.com/16709139/f/278778447.jpg',
        'This is an video',
        'https://photo.tuchong.com/5040418/f/43305517.jpg',
        'https://photo.tuchong.com/3019649/f/302699092.jpg'
      ];
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('SimplePhotoView'),
          ),
          body: Padding(
            padding: const EdgeInsets.all(10.0),
            child: GridView.builder(
              gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
                maxCrossAxisExtent: 300,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
              ),
              itemBuilder: (BuildContext context, int index) {
                final String url = images[index];
                return GestureDetector(
                  child: AspectRatio(
                    aspectRatio: 1.0,
                    child: Hero(
                      tag: url,
                      child: url == 'This is an video'
                          ? Container(
                              alignment: Alignment.center,
                              child: const Text('This is an video'),
                            )
                          : ExtendedImage.network(
                              url,
                              fit: BoxFit.cover,
                            ),
                    ),
                  ),
                  onTap: () {
                    Navigator.of(context).push(TransparentPageRoute(pageBuilder:
                        (BuildContext context, Animation<double> animation,
                            Animation<double> secondaryAnimation) {
                      return PicSwiper(
                        index: index,
                        pics: images,
                      );
                    }));
                  },
                );
              },
              itemCount: images.length,
            ),
          ),
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    二、跳转到Swiper的TransparentPageRoute

    当点击跳转到新的页面的时候,可以使用TransparentPageRoute,该类继承与PageRouteBuilder,实现FadeTransition在点击图片展示预览图片的时候,通过渐隐渐显的方式跳转到下一个路由。

    Widget _defaultTransitionsBuilder(
        BuildContext context,
        Animation<double> animation,
        Animation<double> secondaryAnimation,
        Widget child,
        ) {
      return FadeTransition(
        opacity: CurvedAnimation(
          parent: animation,
          curve: Curves.easeOut,
        ),
        child: child,
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    完整代码如下

    import 'package:flutter/material.dart';
    
    /// Transparent Page Route
    class TransparentPageRoute<T> extends PageRouteBuilder<T> {
      TransparentPageRoute({
        RouteSettings? settings,
        required RoutePageBuilder pageBuilder,
        RouteTransitionsBuilder transitionsBuilder = _defaultTransitionsBuilder,
        Duration transitionDuration = const Duration(milliseconds: 250),
        bool barrierDismissible = false,
        Color? barrierColor,
        String? barrierLabel,
        bool maintainState = true,
      }) : super(
        settings: settings,
        opaque: false,
        pageBuilder: pageBuilder,
        transitionsBuilder: transitionsBuilder,
        transitionDuration: transitionDuration,
        barrierDismissible: barrierDismissible,
        barrierColor: barrierColor,
        barrierLabel: barrierLabel,
        maintainState: maintainState,
      );
    }
    
    Widget _defaultTransitionsBuilder(
        BuildContext context,
        Animation<double> animation,
        Animation<double> secondaryAnimation,
        Widget child,
        ) {
      return FadeTransition(
        opacity: CurvedAnimation(
          parent: animation,
          curve: Curves.easeOut,
        ),
        child: child,
      );
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    三、使用extend_image

    在pubspec.yaml引入extend_image

      # extended_image
      extended_image: ^7.0.2
    
    • 1
    • 2

    当点击图片的时候,传入多张图片,定位到当前的index,多个图片可以左右切换Swiper。这里使用到了ExtendedImageGesturePageView。ExtendedImageGesturePageView与PageView类似,它是为显示缩放/平移图像而设计的。
    如果您已经缓存了手势,请记住在正确的时间调用clearGestureDetailsCache()方法。(例如,页面视图页面被丢弃)

    ExtendedImageGesturePageView属性

    • cacheGesture 保存手势状态(例如在ExtendedImageGesturePageView中,向后滚动时手势状态不会改变),记住clearGestureDetailsCache
    • inPageView 是否在ExtendedImageGesturePageView中

    使用示例

    ExtendedImageGesturePageView.builder(
      itemBuilder: (BuildContext context, int index) {
        var item = widget.pics[index].picUrl;
        Widget image = ExtendedImage.network(
          item,
          fit: BoxFit.contain,
          mode: ExtendedImageMode.gesture,
          gestureConfig: GestureConfig(
            inPageView: true, initialScale: 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: false
          ),
        );
        image = Container(
          child: image,
          padding: EdgeInsets.all(5.0),
        );
        if (index == currentIndex) {
          return Hero(
            tag: item + index.toString(),
            child: image,
          );
        } else {
          return image;
        }
      },
      itemCount: widget.pics.length,
      onPageChanged: (int index) {
        currentIndex = index;
        rebuild.add(index);
      },
      controller: PageController(
        initialPage: currentIndex,
      ),
      scrollDirection: Axis.horizontal,
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    四、使用hero_widget

    当点击图片,实现hero_widget实现hero动画来实现图片预览。
    使用Flutter的Hero widget创建hero动画。 将hero从一个路由飞到另一个路由。 将hero 的形状从圆形转换为矩形,同时将其从一个路由飞到另一个路由的过程中进行动画处理。

    这里使用的hero_widget完整代码如下

    import 'package:extended_image/extended_image.dart';
    import 'package:flutter/material.dart';
    
    /// make hero better when slide out
    class HeroWidget extends StatefulWidget {
      const HeroWidget({
        required this.child,
        required this.tag,
        required this.slidePagekey,
        this.slideType = SlideType.onlyImage,
      });
      final Widget child;
      final SlideType slideType;
      final Object tag;
      final GlobalKey<ExtendedImageSlidePageState> slidePagekey;
      
      _HeroWidgetState createState() => _HeroWidgetState();
    }
    
    class _HeroWidgetState extends State<HeroWidget> {
      RectTween? _rectTween;
      
      Widget build(BuildContext context) {
        return Hero(
          tag: widget.tag,
          createRectTween: (Rect? begin, Rect? end) {
            _rectTween = RectTween(begin: begin, end: end);
            return _rectTween!;
          },
          // make hero better when slide out
          flightShuttleBuilder: (BuildContext flightContext,
              Animation<double> animation,
              HeroFlightDirection flightDirection,
              BuildContext fromHeroContext,
              BuildContext toHeroContext) {
            // make hero more smoothly
            final Hero hero = (flightDirection == HeroFlightDirection.pop
                ? fromHeroContext.widget
                : toHeroContext.widget) as Hero;
            if (_rectTween == null) {
              return hero;
            }
    
            if (flightDirection == HeroFlightDirection.pop) {
              final bool fixTransform = widget.slideType == SlideType.onlyImage &&
                  (widget.slidePagekey.currentState!.offset != Offset.zero ||
                      widget.slidePagekey.currentState!.scale != 1.0);
    
              final Widget toHeroWidget = (toHeroContext.widget as Hero).child;
              return AnimatedBuilder(
                animation: animation,
                builder: (BuildContext buildContext, Widget? child) {
                  Widget animatedBuilderChild = hero.child;
    
                  // make hero more smoothly
                  animatedBuilderChild = Stack(
                    clipBehavior: Clip.antiAlias,
                    alignment: Alignment.center,
                    children: <Widget>[
                      Opacity(
                        opacity: 1 - animation.value,
                        child: UnconstrainedBox(
                          child: SizedBox(
                            width: _rectTween!.begin!.width,
                            height: _rectTween!.begin!.height,
                            child: toHeroWidget,
                          ),
                        ),
                      ),
                      Opacity(
                        opacity: animation.value,
                        child: animatedBuilderChild,
                      )
                    ],
                  );
    
                  // fix transform when slide out
                  if (fixTransform) {
                    final Tween<Offset> offsetTween = Tween<Offset>(
                        begin: Offset.zero,
                        end: widget.slidePagekey.currentState!.offset);
    
                    final Tween<double> scaleTween = Tween<double>(
                        begin: 1.0, end: widget.slidePagekey.currentState!.scale);
                    animatedBuilderChild = Transform.translate(
                      offset: offsetTween.evaluate(animation),
                      child: Transform.scale(
                        scale: scaleTween.evaluate(animation),
                        child: animatedBuilderChild,
                      ),
                    );
                  }
    
                  return animatedBuilderChild;
                },
              );
            }
            return hero.child;
          },
          child: widget.child,
        );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    五、使用Pic_Swiper

    在swiper左右切换功能,使用ExtendedImageGesturePageView来实现切换功能,双击图片及手势进行缩放功能。

    完整代码如下

    typedef DoubleClickAnimationListener = void Function();
    
    class PicSwiper extends StatefulWidget {
      const PicSwiper({
        super.key,
        this.index,
        this.pics,
      });
    
      final int? index;
      final List<String>? pics;
    
      
      _PicSwiperState createState() => _PicSwiperState();
    }
    
    class _PicSwiperState extends State<PicSwiper> with TickerProviderStateMixin {
      final StreamController<int> rebuildIndex = StreamController<int>.broadcast();
      final StreamController<bool> rebuildSwiper =
          StreamController<bool>.broadcast();
      final StreamController<double> rebuildDetail =
          StreamController<double>.broadcast();
      late AnimationController _doubleClickAnimationController;
      late AnimationController _slideEndAnimationController;
      late Animation<double> _slideEndAnimation;
      Animation<double>? _doubleClickAnimation;
      late DoubleClickAnimationListener _doubleClickAnimationListener;
      List<double> doubleTapScales = <double>[1.0, 2.0];
      GlobalKey<ExtendedImageSlidePageState> slidePagekey =
          GlobalKey<ExtendedImageSlidePageState>();
      int? _currentIndex = 0;
      bool _showSwiper = true;
      double _imageDetailY = 0;
      Rect? imageDRect;
    
      
      Widget build(BuildContext context) {
        final Size size = MediaQuery.of(context).size;
        double statusBarHeight = MediaQuery.of(context).padding.top;
    
        imageDRect = Offset.zero & size;
        Widget result = Material(
          color: Colors.transparent,
          shadowColor: Colors.transparent,
          child: Stack(
            fit: StackFit.expand,
            children: <Widget>[
              ExtendedImageGesturePageView.builder(
                controller: ExtendedPageController(
                  initialPage: widget.index!,
                  pageSpacing: 50,
                  shouldIgnorePointerWhenScrolling: false,
                ),
                scrollDirection: Axis.horizontal,
                physics: const BouncingScrollPhysics(),
                canScrollPage: (GestureDetails? gestureDetails) {
                  return _imageDetailY >= 0;
                },
                itemBuilder: (BuildContext context, int index) {
                  final String item = widget.pics![index];
    
                  Widget image = ExtendedImage.network(
                    item,
                    fit: BoxFit.contain,
                    enableSlideOutPage: true,
                    mode: ExtendedImageMode.gesture,
                    imageCacheName: 'CropImage',
                    //layoutInsets: EdgeInsets.all(20),
                    initGestureConfigHandler: (ExtendedImageState state) {
                      double? initialScale = 1.0;
    
                      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!,
                        maxScale: max(initialScale, 5.0),
                        animationMaxScale: max(initialScale, 5.0),
                        initialAlignment: InitialAlignment.center,
                        //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: false,
                      );
                    },
                    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();
                    },
                    loadStateChanged: (ExtendedImageState state) {
                      if (state.extendedImageLoadState == LoadState.completed) {
                        return StreamBuilder<double>(
                          builder:
                              (BuildContext context, AsyncSnapshot<double> data) {
                            return ExtendedImageGesture(
                              state,
                              imageBuilder: (Widget image) {
                                return Stack(
                                  children: <Widget>[
                                    Positioned.fill(
                                      child: image,
                                    ),
                                  ],
                                );
                              },
                            );
                          },
                          initialData: _imageDetailY,
                          stream: rebuildDetail.stream,
                        );
                      }
                      return null;
                    },
                  );
    
                  image = HeroWidget(
                    tag: item,
                    slideType: SlideType.onlyImage,
                    slidePagekey: slidePagekey,
                    child: image,
                  );
    
                  image = GestureDetector(
                    child: image,
                    onTap: () {
                      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);
                },
              ),
              StreamBuilder<bool>(
                builder: (BuildContext c, AsyncSnapshot<bool> d) {
                  if (d.data == null || !d.data!) {
                    return Container();
                  }
    
                  return Positioned(
                    top: statusBarHeight,
                    left: 0.0,
                    right: 0.0,
                    child: MySwiperPlugin(widget.pics, _currentIndex, rebuildIndex),
                  );
                },
                initialData: true,
                stream: rebuildSwiper.stream,
              )
            ],
          ),
        );
    
        result = ExtendedImageSlidePage(
          key: slidePagekey,
          child: result,
          slideAxis: SlideAxis.vertical,
          slideType: SlideType.onlyImage,
          slideScaleHandler: (
            Offset offset, {
            ExtendedImageSlidePageState? state,
          }) {
            return null;
          },
          slideOffsetHandler: (
            Offset offset, {
            ExtendedImageSlidePageState? state,
          }) {
            return null;
          },
          slideEndHandler: (
            Offset offset, {
            ExtendedImageSlidePageState? state,
            ScaleEndDetails? details,
          }) {
            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);
            }
          },
        );
    
        return result;
      }
    
      
      void dispose() {
        rebuildIndex.close();
        rebuildSwiper.close();
        rebuildDetail.close();
        _doubleClickAnimationController.dispose();
        _slideEndAnimationController.dispose();
        clearGestureDetailsCache();
        //cancelToken?.cancel();
        super.dispose();
      }
    
      
      void initState() {
        super.initState();
        _currentIndex = widget.index;
        _doubleClickAnimationController = AnimationController(
            duration: const Duration(milliseconds: 150), vsync: this);
    
        _slideEndAnimationController = AnimationController(
          vsync: this,
          duration: const Duration(milliseconds: 150),
        );
        _slideEndAnimationController.addListener(() {
          _imageDetailY = _slideEndAnimation.value;
          if (_imageDetailY == 0) {
            _showSwiper = true;
            rebuildSwiper.add(_showSwiper);
          }
          rebuildDetail.sink.add(_imageDetailY);
        });
      }
    }
    
    class MySwiperPlugin extends StatelessWidget {
      const MySwiperPlugin(this.pics, this.index, this.reBuild);
    
      final List<String>? pics;
      final int? index;
      final StreamController<int> reBuild;
    
      
      Widget build(BuildContext context) {
        return StreamBuilder<int>(
          builder: (BuildContext context, AsyncSnapshot<int> data) {
            return DefaultTextStyle(
              style: const TextStyle(color: Colors.blue),
              child: Container(
                height: 50.0,
                width: double.infinity,
                // color: Colors.grey.withOpacity(0.2),
                child: Row(
                  children: <Widget>[
                    Container(
                      width: 10.0,
                    ),
                    Text(
                      '${data.data! + 1}',
                    ),
                    Text(
                      ' / ${pics!.length}',
                    ),
                    const SizedBox(
                      width: 10.0,
                    ),
                    const SizedBox(
                      width: 10.0,
                    ),
                    if (!kIsWeb)
                      GestureDetector(
                        child: Container(
                          padding: const EdgeInsets.only(right: 10.0),
                          alignment: Alignment.center,
                          child: const Text(
                            'Save',
                            style: TextStyle(fontSize: 16.0, color: Colors.blue),
                          ),
                        ),
                        onTap: () {
                          // saveNetworkImageToPhoto(pics![index!].picUrl)
                          //     .then((bool done) {
                          //   showToast(done ? 'save succeed' : 'save failed',
                          //       position: const ToastPosition(
                          //           align: Alignment.topCenter));
                          // });
                        },
                      ),
                  ],
                ),
              ),
            );
          },
          initialData: index,
          stream: reBuild.stream,
        );
      }
    }
    
    class ImageDetailInfo {
      ImageDetailInfo({
        required this.imageDRect,
        required this.pageSize,
        required this.imageInfo,
      });
    
      final GlobalKey<State<StatefulWidget>> key = GlobalKey<State>();
    
      final Rect imageDRect;
    
      final Size pageSize;
    
      final ImageInfo imageInfo;
    
      double? _maxImageDetailY;
    
      double get imageBottom => imageDRect.bottom - 20;
    
      double get maxImageDetailY {
        try {
          //
          return _maxImageDetailY ??= max(
              key.currentContext!.size!.height - (pageSize.height - imageBottom),
              0.1);
        } catch (e) {
          //currentContext is not ready
          return 100.0;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376

    使用过程中的util

    import 'package:extended_image/extended_image.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/rendering.dart';
    
    ///
    ///  create by zmtzawqlp on 2020/1/31
    ///
    double? initScale({
      required Size imageSize,
      required Size size,
      double? initialScale,
    }) {
      final double n1 = imageSize.height / imageSize.width;
      final double n2 = size.height / size.width;
      if (n1 > n2) {
        final FittedSizes fittedSizes =
            applyBoxFit(BoxFit.contain, imageSize, size);
        //final Size sourceSize = fittedSizes.source;
        final Size destinationSize = fittedSizes.destination;
        return size.width / destinationSize.width;
      } else if (n1 / n2 < 1 / 4) {
        final FittedSizes fittedSizes =
            applyBoxFit(BoxFit.contain, imageSize, size);
        //final Size sourceSize = fittedSizes.source;
        final Size destinationSize = fittedSizes.destination;
        return size.height / destinationSize.height;
      }
    
      return initialScale;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    效果视频

    六、小结

    flutter开发实战-hero实现图片预览功能extend_image。描述可能不太准确,请见谅。

    学习记录,每天不停进步。

  • 相关阅读:
    Java:PHP与Java——哪个更适合你?
    如何批量旋转图片?学会这三种方法就能轻松实现
    【SQL实用技巧】-- 分组内求topN问题
    Docker下Jenkins打包java项目并部署
    微服务框架 SpringCloud微服务架构 20 RestClient 操作索引库 20.2 hotel 数据结构分析
    【三维目标检测】3DSSD(二)
    BLDC 四大方案
    mac安装navicate
    880. 索引处的解码字符串
    6.SSM-SpringBoot
  • 原文地址:https://blog.csdn.net/gloryFlow/article/details/134011464