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

news2024/9/29 15:32:32

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>[],
    ...
  })

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

实现展示图片GridView

GridView.builder(
          gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 300,
            crossAxisSpacing: 10,
            mainAxisSpacing: 10,
          ),
          itemBuilder: (BuildContext context, int index) {

...

完整代码如下

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,
        ),
      ),
    );
  }
}

二、跳转到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,
  );
}

完整代码如下

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,
  );
}

三、使用extend_image

在pubspec.yaml引入extend_image

  # extended_image
  extended_image: ^7.0.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,
)

四、使用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,
    );
  }
}

五、使用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;
    }
  }
}

使用过程中的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;
}

效果视频

六、小结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1129891.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

预防U盘泄密行为,u盘防复制防拷贝软件——「天锐绿盾」

天锐绿盾数据防泄密系统是一个自主研发的、利用驱动层透明加密技术实现电子文件安全加密的防护产品。它遵循基于文件生命周期安全防护的思想&#xff0c;集成了密码学、访问控制和审计跟踪等技术手段&#xff0c;可以对企事业单位电子文件的存储、访问、传播和处理过程实施全方…

【Sentinel】Sentinel簇点链路的形成

说明 一切节点的跟是 machine-root&#xff0c;同一个资源在不同链路会创建多个DefaultNode&#xff0c;但是在全局只会创建一个 ClusterNode machine-root/\/ \EntranceNode1 EntranceNode2/ \/ \DefaultNode(nodeA) DefaultNode(nodeA)|…

阿里云对象存储OSS文件无法预览,Bucket设置了Referer

您发起的请求头中没有Referer字段或Referer字段为空&#xff0c;与请求Bucket设置的防盗链策略不相符。 解决方案 您可以选择以下任意方案解决该问题。 在请求中增加Referer请求头。 GET /test.txt HTTP/1.1 Date: Tue, 20 Dec 2022 08:48:18 GMT Host: BucketName.oss-examp…

JavaSE ---01 数据类型与运算符

正所谓温故而知新,可以为师矣,虽然已经学过一遍javase,但是回头复习仍然能找到很多初学的时候遗忘的点,所以我们在学习的途中还是要保持空杯心态,这样才能走的更远,切忌眼高手低. 1.变量 说到变量大家都经常去使用,那么什么是变量呢?下面给出变量的定义 变量指的是程序运行时可…

基于windows10的pytorch环境部署及yolov8的安装及测试

第一章 pytorch环境部署留念 第一步&#xff1a;下载安装anaconda 官网地址 &#xff08;也可以到清华大学开源软件镜像站下载&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/&#xff09; 我安装的是下面这个&#xff0c;一通下一步就完事儿。 第二步…

【原创】RockyLinux设置网络/网卡唤醒/NetworkManager设置网络唤醒

前言 由于我的5600G无法安装CentOS系统&#xff0c;因此选择安装了RockyLinux&#xff0c;但是RockyLinux用的是NetworkManager&#xff0c;网上说的都是之前CentOS的方法&#xff0c;因此完全无效&#xff0c;这里来介绍一下RockyLinux如何设置网络唤醒。 修改BIOS设置 我的…

通义大模型使用指南之通义听悟

一、注册 我们可以打开以下网站&#xff0c;用手机号注册一个账号即可。 https://tongyi.aliyun.com/ 二、使用介绍 如图&#xff0c;我们可以看到有三个大项功能&#xff0c;通义千问、通义万相、通义听悟。下来我们体验一下通义听悟的功能。 1、通义听悟 1、1基本功能 当我们…

Java枚举(Enum)的使用

目录 一、枚举类型的定义 二、枚举类型的使用 &#xff08;一&#xff09;、枚举类型的常用方法 &#xff08;二&#xff09;、枚举的简单使用 &#xff08;1&#xff09;、和switch的搭配使用 &#xff08;2&#xff09;、枚举类型的values方法 &#xff08;3&#xff…

JAVA毕业设计103—基于Java+Springboot+vue的药店管理系统(源码+数据库)

基于JavaSpringbootvue的药店管理系统(源码数据库) 一、系统介绍 本系统前后端分离 -功能: 登录、药库药品管理、统计查询、药房管理、物资管理、挂号管理、账号管理、角色管理、权限管理、登录日志管理、药品管理、药品类型管理、客人类型管理 二、所用技术 后端技术栈&a…

PCL 透视投影变换(OpenGL)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在现实生活中,我们总会注意到离我们越远的东西看起来更小。这个神奇的效果被称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的这样: 由于透视的原因,平行线…

优雅草蜻蜓z系统暗影版前台崩溃,后台提示:系统接口异常502,java项目管理yyc-admin后台管理系统服务无法启动的解决方案

蜻蜓z系统暗影版提示系统接口异常502 蜻蜓z系统系统接口异常502&#xff0c;java项目管理yyc-admin后台管理系统服务无法启动&#xff0c;解决方案 系统稳定运行一些时间后突然由于问题造成无法启动&#xff0c;提示接口异常&#xff1a; 原因过程分析&#xff1a; 现象&am…

【Docker】Docker Swarm介绍与环境搭建

为什么不建议在生产环境中使用Docker Compose 多机器如何管理&#xff1f;如何跨机器做scale横向扩展&#xff1f;容器失败退出时如何新建容器确保服务正常运行&#xff1f;如何确保零宕机时间&#xff1f;如何管理密码&#xff0c;Key等敏感数据&#xff1f; Docker Swarm介…

计算机视觉中的数据预处理与模型训练技巧总结

计算机视觉主要问题有图像分类、目标检测和图像分割等。针对图像分类任务&#xff0c;提升准确率的方法路线有两条&#xff0c;一个是模型的修改&#xff0c;另一个是各种数据处理和训练的技巧(tricks)。图像分类中的各种技巧对于目标检测、图像分割等任务也有很好的作用&#…

制造业中的微小缺陷检测——应用场景分析与算法选择(YoloV8/CANet)

一、缺陷检测任务 缺陷检测的任务通常可以分为三个主要阶段&#xff0c;包括缺陷分类、缺陷定位和缺陷分割。 1.缺陷分类 缺陷分类是检测过程的第一步&#xff0c;目的是将检测到的缺陷区域分类为不同的类别&#xff0c;通常是根据缺陷的性质或类型进行分类。分类的类别包括…

深度学习使用Keras进行迁移学习提升网络性能

上一篇文章我们用自己定义的模型来解决了二分类问题,在20个回合的训练之后得到了大约74%的准确率,一方面是我们的epoch太小的原因,另外一方面也是由于模型太简单,结构简单,故而不能做太复杂的事情,那么怎么提升预测的准确率了?一个有效的方法就是迁移学习。 迁移学习其…

C#反射的应用及相关代码示例

在C#编程中&#xff0c;反射是一种强大的工具&#xff0c;它允许程序在运行时动态地获取类型信息、访问和操作类成员。反射为开发人员提供了更大的灵活性和扩展性&#xff0c;使得我们可以编写更加通用和动态的代码。本文将探讨C#反射的应用&#xff0c;并提供一些相关的代码示…

程序员加油!最新最全Java面试题及解答(上百道题,近5w字,包括Redis、MySQL、框架、微服务、消息中间件、集合、jvm,多线程、常见技术场景)

刚看完黑马教程的新版Java面试专题视频教程&#xff0c;java八股文面试全套真题深度详解&#xff08;含大厂高频面试真题&#xff09;&#xff0c;对面试题分专题整理&#xff0c;方便面试突击 Redis相关面试题 Redis相关面试题 面试官&#xff1a;什么是缓存穿透 ? 怎么解决…

软考系列(系统架构师)- 2016年系统架构师软考案例分析考点

试题一 软件架构&#xff08;质量属性、架构风格对比、根据描述填空&#xff09; 试题二 系统开发&#xff08;用例图参与者、用例关系、类图关系&#xff09; 学生、教师、管理员、时间、打印机【问题2】&#xff08;7分&#xff09; 用例是对系统行为的动态描述&#xff0c;用…

【强连通+背包】CF1763E

Problem - E - Codeforces 题意 思路 首先&#xff0c;先考虑第一个条件&#xff0c;要保证是p个节点互相到达且节点数最少&#xff0c;一定是个强连通&#xff0c;图的形态一定就是和强连通相关的。 然后&#xff0c;因为在这个前提上&#xff0c;要让单向节点数尽可能多&a…

归并排序与计数排序(含代码)

目录 目录&#xff1a; 1:归并排序递归 2:归并排序的非递归 3&#xff1a;计数排序的思想 1&#xff1a;归并排序递归 思路&#xff1a;归并排序是采用分治算法的一种排序&#xff0c;将两个有序的子数组合并到一个数组中去使得数组完全有序&#xff0c;所以我们先使子数组有序…