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

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_imageextended_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 outflightShuttleBuilder: (BuildContext flightContext,Animation<double> animation,HeroFlightDirection flightDirection,BuildContext fromHeroContext,BuildContext toHeroContext) {// make hero more smoothlyfinal 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 smoothlyanimatedBuilderChild = 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 outif (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 readyreturn 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.mzph.cn/news/118232.shtml

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

相关文章

【软件测试】自动化测试selenium

目录 一、什么是自动化测试 二、Selenium介绍 1、Selenium是什么 2、Selenium的原理 三、了解Selenium的常用API 1、webDriver API 1.1、元素定位 1.1.1、CSS选择器 1.1.2、Xpath元素定位 1.1.3、面试题 1.2、操作测试对象 1.3、添加等待 1.4、打印信息 1.5、浏…

kvm webvirtcloud 如何添加直通物理机的 USB 启动U盘

第一步&#xff1a;查看USB设备ID 在物理机上输入 lsusb 命令 rootubuntu:/media/usb1# lsusb Bus 002 Device 002: ID 0781:5581 SanDisk Corp. Ultra Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 001 Device 004: ID 0424:2514 Microchip Technolo…

UE4/5 批量进行贴图Texture压缩、修改饱和度

该插件下载地址&#xff1a; &#x1f35e;正在为您运送作品详情https://mbd.pub/o/bread/ZZWYmpxw 适用于 UE4 4.25/4.26/4.27 UE5 以上版本 在Edit - Plugins中分别开启 插件 Python Editor Script Plugin 插件 Editor Scripting Utilites 如果会python代码&#xff0c;…

分享从零开始学习网络设备配置--任务4.2 使用IPv6静态及默认路由实现网络连通

任务描述 某公司利用IPv6技术搭建网络&#xff0c;公司3个部门所有PC机连接在同一交换机上&#xff0c;PC1代表行政部划分到VLAN10中&#xff0c;PC2代表财务部划分到VLAN20中&#xff0c;PC3代表销售部划分到VLAN30中&#xff0c;R1代表公司出口路由器&#xff0c;R2模拟Inter…

统计学习方法 支持向量机(下)

文章目录 统计学习方法 支持向量机&#xff08;下&#xff09;非线性支持向量机与和核函数核技巧正定核常用核函数非线性 SVM 序列最小最优化算法两个变量二次规划的求解方法变量的选择方法SMO 算法 统计学习方法 支持向量机&#xff08;下&#xff09; 学习李航的《统计学习方…

操作系统:计算机系统概述

一战成硕 1.1 手工操作阶段1.2 批处理阶段1.3 分时操作系统1.4 实时操作系统1.5 中断和异常的概念1.6 系统调用 1.1 手工操作阶段 1.2 批处理阶段 单道批处理系统 自动性 顺序性 单道性多道批处理系统 多道 宏观上并行 微观上串行 优点&#xff1a;资源利用率高&#xff0c;多…

Postman —— 配置环境变量

PostMan是一套比较方便的接口测试工具&#xff0c;但我们在使用过程中&#xff0c;可能会出现创建了API请求&#xff0c;但API的URL会随着服务器IP地址的变化而改变。 这样的情况下&#xff0c;如果每一个API都重新修改URL的话那将是非常的麻烦&#xff0c;所以PostMan中也提供…

hive窗口函数记录

记录工作中和学习中的窗口函数&#xff0c;方便以后使用&#xff0c;本记持续更新和完善&#xff0c;版本&#xff1a;231019 文章目录 1.什么是窗口函数2.窗口函数的表达式3.窗口函数的类型1&#xff09; 排名函数2&#xff09; 聚合函数3&#xff09; 跨行取值函数 4.[frame…

如何实现前端实时通信(WebSocket、Socket.io等)?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测(多指标,多图)

回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现IWOA-LSTM改进鲸鱼算法算法优化长短期记忆神经网络的数据回归预测&#xff08;多指标&#xff0c;多图&#…

【图解数据结构】手把手教你如何实现顺序表(超详细)

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板、汇编语言 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️线性表1.1 &#x1f514;线性表的定义1.2 &#x1f514;线性表的存储结构 二. ⛳️…

Premiere Pro(Pr)2023软件下载及安装教程

目录 一.简介 二.安装步骤 软件&#xff1a;Pr版本&#xff1a;2023语言&#xff1a;简体中文大小&#xff1a;8.30G安装环境&#xff1a;Win11/Win10&#xff08;1809版本以上&#xff09;硬件要求&#xff1a;CPU2.6GHz 内存8G(或更高&#xff0c;不支持7代以下CPU&#xf…

【微服务保护】初识 Sentinel —— 探索微服务雪崩问题的解决方案,Sentinel 的安装部署以及将 Sentinel 集成到微服务项目

文章目录 前言一、雪崩问题及其解决方案1.1 什么是雪崩问题1.2 雪崩问题的原因1.3 解决雪崩问题的方法1.4 总结 二、初识 Sentinel 框架2.1 什么是 Sentinel2.2 Sentinel 和 Hystrix 的对比 三、Sentinel 的安装部署四、集成 Sentinel 到微服务 前言 微服务架构在现代软件开发…

如何利用数字化转型升级,重塑企业核心竞争力?

工程机械行业是一个周期性明显的行业&#xff0c;企业经营受到宏观经济与国家基础设施建设的影响较大&#xff0c;例如企业经济上行时&#xff0c;加大投资扩大生产规模&#xff0c;以满足市场需求的增长&#xff0c;当经济下行时&#xff0c;企业可能面临减产和裁员等问题&…

Ubuntu 安装 npm 和 node

前言 最近学习VUE&#xff0c;在ubuntu 2204 上配置开发环境&#xff0c;涉及到npm node nodejs vue-Cli脚手架等内容&#xff0c;做以记录。 一、node nodejs npm nvm 区别 &#xff1f; node 是框架&#xff0c;类似python的解释器。nodejs 是编程语言&#xff0c;是js语言的…

手机游戏定制研发手机软件开发

手机游戏定制研发是一个多阶段的过程&#xff0c;它使开发者能够根据客户的需求和创意&#xff0c;构建独特的游戏体验。这个领域是一个蓬勃发展的市场&#xff0c;因为手机游戏在全球范围内都备受欢迎。在本文中&#xff0c;我们将深入探讨手机游戏定制研发的主要步骤以及关键…

Day22

1、分别占据栈底&#xff0c;共用栈顶&#xff0c;对着入栈 2、循环队列不预留一个空间 3、直接定址法线性探测&#xff08;可能占别人的位置&#xff09; 4、直接选择无关顺序&#xff0c;选出max/min

UGO+DRS评复之路

前言 针对数据库整体迁移方案&#xff0c;为解决异构平台数据库迁移&#xff0c;为减轻迁移人员的工作强度以及迁移周期。华为云GaussDB迁移UGO&DRS迁移工具应运而生。 UGO介绍 数据库和应用迁移&#xff08;Database and Application Migration UGO&#xff0c;简称为UG…

SparkSQL执行流程与Catalyst优化器

目录 一、SparkSQL运行流程与Catalyst优化器 &#xff08;1&#xff09;RDD运行流程 &#xff08;2&#xff09;SparkSQL自动优化 &#xff08;3&#xff09;Catalyst优化器流程 &#xff08;4&#xff09;Catalyst优化器总结 &#xff08;5&#xff09;Spark SQL执行流程 一、…

HttpClient远程使用大全

一 HttpClient简介 1.1 概述 HttpClient只能以编程的方式通过其API用于传输和接受HTTP消息。主要实现功能&#xff1a; 实现了所有 HTTP 的方法&#xff08;GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等&#xff09; 支持 HTTPS 协议 支持代理服务器&#xff08;Nginx…