简单讲下鸿蒙平台的实现,用2个Canvas组成的,一个负责绘制图片,一个负责绘制裁剪框。
Stack(){Canvas(this.imageContext)Canvas(this.cropLayerContext)}值得注意的是与Flutter不同,鸿蒙中Canvas的位置坐标系是基于本身的,也就是说是(0,0)开始的,不是基于整个屏幕。
使用GestureGroup包含了PinchGesture和PanGesture两种手势。PinchGesture是控制图片缩放;PanGesture是控制图片的移动,也控制裁剪框的移动。
.gesture(GestureGroup(GestureMode.Exclusive,PinchGesture({}).onActionStart((event:GestureEvent)=>{this.handleScaleStart(event,false,);}).onActionUpdate((event:GestureEvent)=>{this.handleScaleUpdate(event,false);}),PanGesture().onActionStart((event:GestureEvent)=>{this.handleScaleStart(event,true);}).onActionUpdate((event:GestureEvent)=>{this.handleScaleUpdate(event,true);}).onActionEnd((event:GestureEvent)=>{if(this._cropRectMoveType!=null){this._cropRectMoveType=null;//movetocenterletoldScreenCropRect=this._actionDetails!.cropRect!;//notmoveif(OffsetUtils.isSame(oldScreenCropRect.center,this._actionDetails!.cropRectLayoutRectCenter)){return;}letcenterCropRect=getDestinationRect(this._actionDetails!.cropRectLayoutRect!,oldScreenCropRect.size,);this._startCropRectAutoCenterAnimation(oldScreenCropRect,centerCropRect);}})))手势和移动的代码处理跟Flutter平台一样,感兴趣的小伙伴可以自行查看。
要确定是裁剪框移动还是图片移动,我们需要判断是否手势点击是在裁剪框的范围内。在onTouch做处理,通过点击的点是否在裁剪框的区域内部来判断,由touchOnCropRect方法完成。
.onTouch((event)=>{if(event.type==TouchType.Down){this._pointerDown=true;if(event.touches.length==1){lettouch=event.touches[0];this._cropRectMoveType=this.touchOnCropRect(newgeometry.Offset(touch.x,touch.y));}this._drawLayer();}elseif(event.type==TouchType.Up||event.type==TouchType.Cancel){if(this._pointerDown){this._pointerDown=false;this._drawLayer();this._saveCurrentState();}}})具体处理为,判断是否在裁剪框+-hitTestSize之后的内外框范围,当我们点击在屏幕上面的时候判断是否点击在了裁剪框的区域里面。
CanvasRenderingContext2D的绘制,只支持Matrix2D。导致最终实现镜像效果的时候,我只能对Canvas组件进行matrix4transform
所以这部分,直接从Flutter中移植了过来。值得注意的是比较的时候精度问题,定义一个precisionErrorTolerance,当2者差距的绝对值小于precisionErrorTolerance的时候认为相等。
staticprecisionErrorTolerance=1e-10;Matrix4Matrix4Transit的实现跟Flutter中的Matrix4,效果不一样,而也没办法修改,所以从Flutter中移植Matrix4过来。
Matrix4是整个边界计算和最终图片输出的核心。
_startCropRectAutoCenterAnimation(begin:geometry.Rect,end:geometry.Rect):void{letoptions:AnimatorOptions={duration:this._config!.cropRectAutoCenterAnimationDuration,easing:"linear",delay:0,fill:"forwards",direction:"normal",iterations:1,begin:0,end:1,};this._cropRectAutoCenterRect=newRectTween(begin,end);this._cropRectAutoCenterAnimator=animator.create(options);this._cropRectAutoCenterAnimator!.onFrame=(value)=>{this._isAnimating=true;if(this._cropRectAutoCenterRect!=undefined){this._updateUIIfNeed(()=>{this._doCropAutoCenterAnimation(this._cropRectAutoCenterRect!.transform(value));});}};this._cropRectAutoCenterAnimator.onFinish=this._cropRectAutoCenterAnimator.onCancel=()=>{this._cropRectAutoCenterAnimator=undefined;this._cropRectAutoCenterRect=undefined;this._isAnimating=false;this._saveCurrentState();};this._cropRectAutoCenterAnimator.play();}Colorarkts中有各种Color,组件的颜色是ResourceColor
declaretypeResourceColor=Color|number|string|Resource;其中
支持rgb或者argb。示例:0xffffff,0xffff0000。number无法识别传入位数,格式选择依据值的大小,例如0x00ffffff作rgb格式解析
示例:'#ffffff','#ff000000','rgb(255,100,255)','rgba(255,100,255,0.5)'。
而Canvas的颜色为下面类型。
string|number|CanvasGradient|CanvasPattern;if(typeofcolor==="string"||typeofcolor==="number"){returncolor;}特别注意的是:sys.color.brand在被打成静态har包之后,会获取到错误的resourceid1,发生{"code":9001001,"message":"GetColorByIdfailedstate"}错误,code解释为:InvalidresourceID.解决方案是通过onWillApplyTheme中得到的Theme来获取正确的ID。即(theme.colors.brandasResource).id,正确的ID应该125830976。
.transform(matrix4.identity().rotate({y:this._rotationYRadians!=01:0,x:0,z:0,angle:NumberUtils.degrees(this._rotationYRadians),}))ChangeNotifier不管在什么平台,我对EventBus是无感的,鸿蒙也有类似的。ChangeNotifier直接从Flutter中移植过来,用于监听变化,组件中用于通知裁剪历史变化。
我在本地例子里面是使用import*asimage_cropperfrom"@candies/image_cropper";
image_cropper.ImageCropper({image:this.image,config:this.config,})本地运行正常,但是通过ohpminstall@candies/image_cropper安装之后,运行报错。
1ERROR:ArkTS:ERRORFile:】MyApplication4/entry/src/main/ets/pages/Index.ets:24:9'image_cropper.ImageCropper({image:this.image,config:this.config,})'doesnotmeetUIcomponentsyntax.COMPILERESULT:FAIL{ERROR:2WARN:5}>hvigorERROR:BUILDFAILEDin479ms解决方案是引用方式改下下面的方法:
import{ImageCropper}from"@candies/image_cropper";
使用如下:
import*asimage_cropperfrom"@candies/image_cropper";import{ImageCropper}from"@candies/image_cropper";import{image}from'@kit.ImageKit';@Entry@ComponentstructIndex{@Stateimage:image.ImageSource|undefined=undefined;privatecontroller:image_cropper.ImageCropperController=newimage_cropper.ImageCropperController();@Stateconfig:image_cropper.ImageCropperConfig=newimage_cropper.ImageCropperConfig({maxScale:8,cropRectPadding:image_cropper.geometry.EdgeInsets.all(20),controller:this.controller,initCropRectType:image_cropper.InitCropRectType.imageRect,cropAspectRatio:image_cropper.CropAspectRatios.custom,});build(){Column(){if(this.image!=undefined){ImageCropper({image:this.image,config:this.config,})}}}}安装你可以通过下面的命令安装改组件。
ohpminstall@candies/image_cropper
exportclassCropAspectRatios{///Noaspectratioforcrop;free-formcroppingisallowed.staticcustom:number|null=null;///Thesameastheoriginalaspectratiooftheimage.///ifit'sequalorlessthan0,itwillbetreatedasoriginal.staticoriginal:number=0.0;///Aspectratioof1:1(square).staticratio1_1:number=1.0;///Aspectratioof3:4(portrait).staticratio3_4:number=3.0/4.0;///Aspectratioof4:3(landscape).staticratio4_3:number=4.0/3.0;///Aspectratioof9:16(portrait).staticratio9_16:number=9.0/16.0;///Aspectratioof16:9(landscape).staticratio16_9:number=16.0/9.0;}裁剪图层Painter你现在可以通过覆写[ImageCropperConfig.cropLayerPainter]里面的方法来自定裁剪图层.
exportclassImageCropperLayerPainter{///Painttheentirecroplayer,includingmask,lines,andcorners///Therectmaybebiggerthansizewhenwerotatecroprect.///Adjusttherecttoensurethemaskcoversthewholeareaafterrotationpublicpaint(config:ImageCropperLayerPainterConfig):void{//Drawthemasklayerthis.paintMask(config);//Drawthegridlinesthis.paintLines(config);//Drawthecornersofthecropareathis.paintCorners(config);}///DrawcornersofthecropareaprotectedpaintCorners(config:ImageCropperLayerPainterConfig):void{}///DrawthemaskoverthecropareaprotectedpaintMask(config:ImageCropperLayerPainterConfig):void{}///DrawgridlinesinsidethecropareaprotectedpaintLines(config:ImageCropperLayerPainterConfig):void{}}裁剪控制器ImageCropperController提供旋转,翻转,撤销,重做,重置,重新设置裁剪比例,获取裁剪之后图片数据等操作。
exportinterfaceFlipOptions{animation:boolean,duration:number,}flip(options:FlipOptions)controller.flip();旋转参数描述默认animation是否开启动画falseduration动画时长200millisecondsdegree旋转角度90rotateCropRect是否旋转裁剪框true当rotateCropRect为true并且degree为90度时,裁剪框也会跟着旋转。
exportinterfaceRotateOptions{degree:number,animation:boolean,duration:number,rotateCropRect:boolean,}rotate(options:RotateOptions)controller.rotate();重新设置裁剪比例重新设置裁剪框的宽高比
controller.updateCropAspectRatio(CropAspectRatios.ratio4_3);撤消撤销上一步操作
//判断是否能撤销boolcanUndo=controller.canUndo;//撤销controller.undo();重做重做下一步操作
//判断是否能重做boolcanRedo=controller.canRedo;//重做controller.redo();重置重置所有操作
controller.reset();历史//获取当前是第几个操作controller.getCurrentIndex();//获取操作历史controller.getHistory();//保存当前状态controller.saveCurrentState();//获取当前操作对应的配置controller.getCurrentConfig();裁剪数据获取裁剪之后的图片数据,返回一个PixelMap对象
controller.getCroppedImage();获取状态信息controller.getActionDetails();controller.rotateDegrees;controller.cropAspectRatio;controller.originalCropAspectRatio;controller.originalCropAspectRatio;结语PoorArkTS,NoExtension,NoOperator,NoNamedParameters。
在迁移过程中,你需要面对的主要是不同语言的差异化,不能说谁好谁不好,只是大家更习惯谁而已。
目前在鸿蒙平台,总共迁移了5个库过去,如果你也想贡献鸿蒙平台,欢迎加入HarmonyCandies,一起共建鸿蒙平台社区生态。