基于安卓平台的图片裁切组件 crop_image_layout,实现了鸿蒙化迁移和重构,代码已经开源,目前已经获得了很多人的 Star 和 Fork ,欢迎各位下载使用并提出宝贵意见!

https://gitee.com/isrc_ohos/crop_image_layout_ohos
crop_image_layout_ohos 组件能对图片进行旋转和自定义裁切的操作,并且无论待裁切图片原尺寸有多大或多小,最终都将在以最佳尺寸在组件内显示。
同时,该组件操作界面简洁且使用方法简单,易被开发者使用或优化,能够提升应用的丰富性和可操作性。
组件效果展示

图 1:crop_image_layout_ohos 组件的运行效果图
对应运行效果图,详细解释其主要提供的功能:
点击“rotate”按钮可以对图片进行旋转操作。
手指按住裁切框内任意处并拖动,可实现裁切框移动,裁切框停止移动时,框内的图片即为想要裁切的图片。
被选中区域的左上角和右下角坐标会在图片下方的文本框中进行显示;
点击“crop”按钮可对选中的图片区域进行裁切,之后会跳转到第二个界面显示裁切后的图片。
Sample 解析
如下图:

裁切框:负责划定图片的裁切区域。
裁切图片:是指被导入组件中,即将被裁切的图片。
组件区域:是指组件所在的位置。
步骤 1:在 xml 文件中添加 EditPhotoView 控件。
步骤 2:导入所需类并实例化类对象。
步骤 3:将裁切框坐标数据设置到裁切图片。
步骤 4:将裁切图片和裁切框添加到布局中
步骤 5:显示裁切框左上角和右下角坐标值。
步骤 6:设置监听事件。
并分别设置裁切框拐角和边的颜色以及裁切图片未被选中部分的阴影颜色,如图所示。

图 3:属性设置示意图
<com.huawei.croplayout.EditPhotoView//添加组件区域
ohos:id="$+id:editable_image"
...
crop:crop_corner_color="#45B4CA"//裁切框拐角颜色
crop:crop_line_color="#d7af55" //裁切框边颜色
crop:crop_shadow_color="#77ffffff"/> //裁切图片未被选中部分的阴影颜色
onBoxChangedListener 类用于监听裁切框变化
EditPhotoView 类用于设置组件区域
EdittableImage 类用于设置裁切图片
ScalableBox 类用于设置裁切框
import com.example.croplayout.handler.OnBoxChangedListener;//裁切框变化监听
import com.example.croplayout.EditPhotoView;//组件区域
import com.example.croplayout.EditableImage;//裁切图片
import com.example.croplayout.model.ScalableBox;//裁切框
新实例化一个左上角坐标为(25,180)、右下角坐标为(640,880)的裁切框对象,并调用 add() 方法将其添加到上述 boxes 中。
//用于绑定组件的裁切图片视图区域
final EditPhotoView imageView = (EditPhotoView) findComponentById(ResourceTable.Id_editable_image);
final Text boxText = (Text) findComponentById(ResourceTable.Id_box_text);
final EditableImage image = new EditableImage(this, ResourceTable.Media_photo2);
List<ScalableBox> boxes = new ArrayList<>();//用于设置裁切框的坐标
boxes.add(new ScalableBox(25, 180, 640, 880));//裁切框的坐标
通过 setBoxes() 方法将 boxes 中裁切框对象的坐标数据设置到裁切图片 image 中,实现裁切框相对裁切图片的位置设定。
image.setBoxes(boxes);
调用 intView() 方法,创建裁切图片和裁切框的视图,并将其添加到组件布局中进行显示。
imageView.initView(this, image);
重新声明一个 ScalableBox 类型的对象 activeBox,用于动态取裁切框的坐标,并将其通过 Text 在界面上显示出来。
ScalableBox activeBox = image.getActiveBox();//动态获取图片中裁切框选取区域的坐标
boxText.setText("box: [" + activeBox.getX1() + "," + activeBox.getY1() +
"],[" + activeBox.getX2() + "," + activeBox.getY2() + "]");
组件区域监听事件:为组件区域对象 imageView 设置监听事件,当裁切框位置发生变化时,将其坐标设置到 Text 对象 boxText 中进行显示。
imageView.setOnBoxChangedListener(new OnBoxChangedListener() {
@Override//设置裁切框区域监听事件
public void onChanged(int x1, int y1, int x2, int y2) {
boxText.setText("box: [" + x1 + "," + y1 + "],[" + x2 + "," + y2 + "]");
}
});
为其设置点击监听事件,按钮被点击时,通过组件区域对象 imageView 调用 rotateImageView() 方法实现裁切图片向右旋转 90° 的效果。
Button rotateButton = (Button) findComponentById(ResourceTable.Id_rotate_button);//与”rorate_button“控件绑定
rotateButton.setClickedListener(new Component.ClickedListener() {
@Override//设置点击监听事件
public void onClick(Component component) {
imageView.rotateImageView();//实现裁切图片向右旋转90°
}
});
通过 Intent 跳转到第二个界面,并将裁切后的图片作为参数传入,显示在第二个界面中。
Button cropButton = (Button) findComponentById(ResourceTable.Id_crop_button);
cropButton.setClickedListener(new Component.ClickedListener() {
@Override//设置点击监听事件
public void onClick(Component component) {
PixelMap croppedImage = image.cropOriginalImage();
Intent newIntent = new Intent();
newIntent.setParam("image", croppedImage);
present(new SecondAbilitySlice(), newIntent);
}
});
Library 解析
如下图:

其中,左上角坐标对应图 4 中的(X1,Y1),右上角对应图 4 中的(X2,Y2),通过设置裁切框对角线上两个点,就可以唯一确定其大小和位置了。
boxes.add(new ScalableBox(25, 180, 640, 880));
实例化过程需要通过 ScalableBox 类的构造函数,设置裁切框左上角和右下角的坐标。
public ScalableBox(int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
其中,activeBoxIdx 是指在 boxes 中盛纳裁切框坐标的下标,List 可以为用户预留多个裁切框坐标,本组件中只是用下表为 0 的裁切框坐标。
public void setBoxes(List<ScalableBox> boxes, int activeBoxIdx) {
if (boxes != null && boxes.size() > 0) {//如果boxes对象不为空且尺寸大于0
this.originalBoxes = boxes;
copyOfActiveBox = new ScalableBox();
copyOfActiveBox.setX1(originalBoxes.get(activeBoxIdx).getX1());
copyOfActiveBox.setX2(originalBoxes.get(activeBoxIdx).getX2());
copyOfActiveBox.setY1(originalBoxes.get(activeBoxIdx).getY1());
copyOfActiveBox.setY2(originalBoxes.get(activeBoxIdx).getY2());
}
}
分别通过 setViewSize() 方法设置裁切图片视图区域尺寸、setPixelMap() 为其设置裁切图片位图格式、setScaleMode() 方法为其设置图片缩放模式为中心缩放、setBoxSize() 方法设置裁切图片和裁切框适配后的尺寸。
public void initView(Context context, EditableImage editableImage) {
this.editableImage = editableImage;
selectionView = new SelectionView(context,
lineWidth, cornerWidth, cornerLength,
lineColor, cornerColor, dotColor, shadowColor, editableImage);
imageView = new Image(context);//设置选择区域尺寸、边角尺寸以及颜色
imageView.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));//跟随父组件
selectionView.setLayoutConfig(new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT));
addComponent(imageView, 0);//将裁切框区域和选择区域添加到布局中
addComponent(selectionView, 1);
if (editableImage != null) {
editableImage.setViewSize(mWidth, mHeight);
imageView.setPixelMap(editableImage.getOriginalPixelMap());
imageView.setScaleMode(Image.ScaleMode.ZOOM_CENTER);//中心缩放模式
selectionView.setBoxSize(editableImage, editableImage.getBoxes(), mWidth, mHeight);
}
}
这是为了更好地在裁切图片视图区域中展示图片,无论过大或过小尺寸的图片都能在此区域中被缩放至最合适的程度显示。原理可参考图 5。

计算完成后将图片放大后的宽和高分别存放在 int 型数组 fitSize[] 中。上述是以原裁切图片尺寸小于裁切图片视图区域为例,反之同理。
public int[] getFitSize() {//适配图片,将图片缩放至比例与裁切图片视图区域比例一致
int[] fitSize = new int[2];//用于存放适配后的图片宽高
//原裁剪图片宽高比
float ratio = originalPixelMap.getImageInfo().size.width / (float) originalPixelMap.getImageInfo().size.height;
float viewRatio = viewWidth / (float) viewHeight;//裁切图片视图区域宽高比
//原裁剪图片宽和高比例大于裁切图片视图区域宽和高比例
if (ratio > viewRatio) {
float factor = viewWidth / (float) originalPixelMap.getImageInfo().size.width;//裁切图片宽放大的倍数
fitSize[0] = viewWidth;//宽为裁切图片视图区域宽
fitSize[1] = (int) (originalPixelMap.getImageInfo().size.height * factor);//根据宽放大的倍数计算放大后高的长度
} else { //原裁剪图片宽和高比例小于裁切图片视图区域宽和高比例
float factor = viewHeight / (float) originalPixelMap.getImageInfo().size.height;
fitSize[0] = (int) (originalPixelMap.getImageInfo().size.width * factor);
fitSize[1] = viewHeight;
}
return fitSize;
}
上述功能是由 SelectionView 类的 setBoxsize() 方法。获取适配后图片的宽高,与裁切框宽高进行计算得到 originX 和 originY,并调用 setDisplayBoxes() 方法设置适配后裁切框的坐标。
public void setBoxSize(EditableImage editableImage, List<ScalableBox> originalBoxes, int widthX, int heightY) {
int[] fitSize = editableImage.getFitSize();//获取前面计算地适配后的图片尺寸
this.pixelMapWidth = fitSize[0];//适配后图片的宽
this.pixelMapHeight = fitSize[1];//适配后图片地高
int originX = (widthX - pixelMapWidth) / 2;
int originY = (heightY - pixelMapHeight) / 2;
this.originX = originX;
this.originY = originY;
setDisplayBoxes(originalBoxes);//设置适配后裁切框的坐标
invalidate();
}
再加上前面计算好的 originX,即得到适配后的裁切框左上角横坐标 scaleX1,右下角横坐标 scaleX2、左上角竖坐标 scaleY1、右下角竖坐标 scaleY2 同理。
float scale = ((float) editableImage.getFitSize()[0]) / editableImage.getActualSize()[0];
int scaleX1 = (int) Math.ceil((originalBox.getX1() * scale) + originX);
int scaleX2 = (int) Math.ceil((originalBox.getX2() * scale) + originX);
int scaleY1 = (int) Math.ceil((originalBox.getY1() * scale) + originY);
int scaleY2 = (int) Math.ceil((originalBox.getY2() * scale) + originY);
//将适配后的裁切框重新加入到裁切图片中
displayBox.setX1(scaleX1);
displayBox.setX2(scaleX2);
displayBox.setY1(scaleY1);
displayBox.setY2(scaleY2);
项目贡献人:王时予、李珂、朱伟、郑森文、陈美汝

求分享

求点赞

求在看




