From 57c36b09ac3a05ad9de791a5a27e0e98c3688ada Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 26 Apr 2026 19:23:28 +0800 Subject: [PATCH] =?UTF-8?q?=E5=90=88=E5=B9=B6=E8=A3=81=E5=89=AA=E6=A1=86?= =?UTF-8?q?=E5=88=B0=E7=94=BB=E5=B8=83=E6=8E=A7=E4=BB=B6=E5=B9=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E4=B8=A4=E6=8C=87=E7=BC=A9=E6=94=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将剪裁框从CropOverlayView移到CropCanvasView - 删除独立的剪裁窗口CropOverlayView - 添加两指缩放剪裁框大小的功能 --- .../winboll/studio/gallery/CropActivity.java | 19 +- .../studio/gallery/CropCanvasView.java | 201 ++++++++++++++++++ gallery/src/main/res/layout/activity_crop.xml | 9 +- 3 files changed, 211 insertions(+), 18 deletions(-) diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java b/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java index 0027525..f1eef7c 100644 --- a/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java +++ b/gallery/src/main/java/cc/winboll/studio/gallery/CropActivity.java @@ -7,7 +7,6 @@ import android.net.Uri; import android.os.Bundle; import android.provider.MediaStore; import android.view.View; -import android.widget.FrameLayout; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import cc.winboll.studio.libappbase.LogUtils; @@ -23,7 +22,6 @@ public class CropActivity extends AppCompatActivity { public static final String EXTRA_CROP_HEIGHT = "crop_height"; private CropCanvasView cropCanvasView; - private CropOverlayView cropOverlay; private Bitmap originalBitmap; private String imagePath; private String albumPath; @@ -65,7 +63,6 @@ public class CropActivity extends AppCompatActivity { }); cropCanvasView = findViewById(R.id.crop_canvas_view); - cropOverlay = findViewById(R.id.crop_overlay); loadImage(); } @@ -85,7 +82,7 @@ public class CropActivity extends AppCompatActivity { cropCanvasView.post(new Runnable() { @Override public void run() { - initCropOverlay(); + initCrop(); } }); } else { @@ -99,14 +96,14 @@ public class CropActivity extends AppCompatActivity { } } - private void initCropOverlay() { + private void initCrop() { cropCanvasView.initCanvas(originalBitmap.getWidth(), originalBitmap.getHeight(), cropRatio); - int canvasW = cropCanvasView.getCanvasWidth(); - int canvasH = cropCanvasView.getCanvasHeight(); - - cropOverlay.setTargetRatio(cropRatio); - cropOverlay.initCanvas(canvasW, canvasH); + int viewW = cropCanvasView.getWidth(); + int viewH = cropCanvasView.getHeight(); + if (viewW > 0 && viewH > 0) { + cropCanvasView.scaleToView(viewW, viewH); + } } private void saveCroppedCover() { @@ -116,7 +113,7 @@ public class CropActivity extends AppCompatActivity { } try { - RectF cropRect = cropOverlay.getCropRect(); + RectF cropRect = cropCanvasView.getCropRect(); RectF canvasBounds = cropCanvasView.getCanvasBounds(); RectF imageBounds = cropCanvasView.getImageBounds(); diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java b/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java index 7f44b65..7cfb626 100644 --- a/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java +++ b/gallery/src/main/java/cc/winboll/studio/gallery/CropCanvasView.java @@ -7,10 +7,23 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; public class CropCanvasView extends View { private Paint imagePaint; + private Paint borderPaint; + private Paint cornerPaint; + private RectF cropRect; + private int touchArea = 50; + + private float lastX, lastY; + private int activeCorner = -1; + private static final int CORNER_TOP_LEFT = 0; + private static final int CORNER_TOP_RIGHT = 1; + private static final int CORNER_BOTTOM_LEFT = 2; + private static final int CORNER_BOTTOM_RIGHT = 3; + private static final int CORNER_CENTER = 4; private int imageWidth; private int imageHeight; @@ -20,10 +33,12 @@ public class CropCanvasView extends View { private int extendWidth; private int canvasWidth; private int canvasHeight; + private float minSize = 50; private RectF imageBounds = new RectF(); private RectF canvasBounds = new RectF(); private Bitmap originalBitmap; + private float initialSpan; public CropCanvasView(Context context) { super(context); @@ -43,6 +58,15 @@ public class CropCanvasView extends View { private void init() { imagePaint = new Paint(); imagePaint.setFilterBitmap(true); + + borderPaint = new Paint(); + borderPaint.setColor(Color.parseColor("#CCAA00")); + borderPaint.setStyle(Paint.Style.STROKE); + borderPaint.setStrokeWidth(3); + + cornerPaint = new Paint(); + cornerPaint.setColor(Color.WHITE); + cornerPaint.setStyle(Paint.Style.FILL); } public void setImageBitmap(Bitmap bitmap) { @@ -72,6 +96,8 @@ public class CropCanvasView extends View { imageBounds.set(left, top, left + imageWidth, top + imageHeight); canvasBounds.set(0, 0, canvasWidth, canvasHeight); + cropRect = new RectF(0, 0, canvasWidth, canvasHeight); + requestLayout(); invalidate(); } @@ -79,6 +105,8 @@ public class CropCanvasView extends View { public void scaleToView(int viewWidth, int viewHeight) { if (viewWidth > 0 && viewHeight > 0 && canvasWidth > 0 && canvasHeight > 0) { float scale = Math.max((float) viewWidth / canvasWidth, (float) viewHeight / canvasHeight); + int oldCanvasW = canvasWidth; + int oldCanvasH = canvasHeight; canvasWidth = (int) (canvasWidth * scale); canvasHeight = (int) (canvasHeight * scale); @@ -87,11 +115,24 @@ public class CropCanvasView extends View { imageBounds.set(left, top, left + imageWidth, top + imageHeight); canvasBounds.set(0, 0, canvasWidth, canvasHeight); + if (cropRect != null) { + float scaleX = (float) canvasWidth / oldCanvasW; + float scaleY = (float) canvasHeight / oldCanvasH; + cropRect.left *= scaleX; + cropRect.top *= scaleY; + cropRect.right *= scaleX; + cropRect.bottom *= scaleY; + } + requestLayout(); invalidate(); } } + public RectF getCropRect() { + return new RectF(cropRect); + } + public int getCanvasWidth() { return canvasWidth; } @@ -136,5 +177,165 @@ public class CropCanvasView extends View { if (originalBitmap != null && !originalBitmap.isRecycled()) { canvas.drawBitmap(originalBitmap, imageBounds.left, imageBounds.top, imagePaint); } + + if (cropRect != null) { + canvas.drawRect(cropRect, borderPaint); + + canvas.drawCircle(cropRect.left, cropRect.top, 12, cornerPaint); + canvas.drawCircle(cropRect.right, cropRect.top, 12, cornerPaint); + canvas.drawCircle(cropRect.left, cropRect.bottom, 12, cornerPaint); + canvas.drawCircle(cropRect.right, cropRect.bottom, 12, cornerPaint); + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (cropRect == null || canvasBounds.isEmpty()) return super.onTouchEvent(event); + + if (event.getPointerCount() == 2) { + int action = event.getActionMasked(); + if (action == MotionEvent.ACTION_POINTER_DOWN) { + initialSpan = getSpan(event); + return true; + } else if (action == MotionEvent.ACTION_MOVE) { + float span = getSpan(event); + if (initialSpan > 0) { + float scale = span / initialSpan; + float centerX = cropRect.centerX(); + float centerY = cropRect.centerY(); + float newWidth = cropRect.width() * scale; + float newHeight = newWidth / coverRatio; + + newWidth = Math.max(minSize, Math.min(newWidth, canvasBounds.width())); + newHeight = newWidth / coverRatio; + + cropRect.set(centerX - newWidth / 2, centerY - newHeight / 2, + centerX + newWidth / 2, centerY + newHeight / 2); + + initialSpan = span; + invalidate(); + } + return true; + } + } + + float x = event.getX(); + float y = event.getY(); + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + lastX = x; + lastY = y; + activeCorner = getActiveCorner(x, y); + return true; + + case MotionEvent.ACTION_MOVE: + float dx = x - lastX; + float dy = y - lastY; + + if (activeCorner == CORNER_CENTER) { + float newLeft = cropRect.left + dx; + float newTop = cropRect.top + dy; + float newRight = cropRect.right + dx; + float newBottom = cropRect.bottom + dy; + + if (newLeft >= canvasBounds.left && newRight <= canvasBounds.right) { + cropRect.left = newLeft; + cropRect.right = newRight; + } + if (newTop >= canvasBounds.top && newBottom <= canvasBounds.bottom) { + cropRect.top = newTop; + cropRect.bottom = newBottom; + } + } else if (activeCorner == CORNER_TOP_LEFT) { + adjustCorner(cropRect.left + dx, cropRect.top + dy, true, true); + } else if (activeCorner == CORNER_TOP_RIGHT) { + adjustCorner(cropRect.right + dx, cropRect.top + dy, false, true); + } else if (activeCorner == CORNER_BOTTOM_LEFT) { + adjustCorner(cropRect.left + dx, cropRect.bottom + dy, true, false); + } else if (activeCorner == CORNER_BOTTOM_RIGHT) { + adjustCorner(cropRect.right + dx, cropRect.bottom + dy, false, false); + } + + lastX = x; + lastY = y; + invalidate(); + return true; + + case MotionEvent.ACTION_UP: + activeCorner = -1; + return true; + } + return super.onTouchEvent(event); + } + + private void adjustCorner(float nx, float ny, boolean left, boolean top) { + float newWidth; + float newLeft = cropRect.left; + float newTop = cropRect.top; + float newRight = cropRect.right; + float newBottom = cropRect.bottom; + + if (left) { + newWidth = cropRect.width() - (nx - cropRect.left); + newLeft = Math.max(canvasBounds.left, Math.min(nx, cropRect.right - minSize)); + } else { + newWidth = nx - cropRect.left; + newRight = Math.min(canvasBounds.right, Math.max(nx, cropRect.left + minSize)); + newLeft = cropRect.left; + } + + float newHeight = newWidth / coverRatio; + + if (top) { + newTop = Math.max(canvasBounds.top, Math.min(cropRect.bottom - minSize, cropRect.bottom - newHeight)); + newBottom = newTop + newHeight; + } else { + newBottom = Math.min(canvasBounds.bottom, Math.max(cropRect.top + minSize, cropRect.top + newHeight)); + newTop = newBottom - newHeight; + } + + if (left) { + cropRect.left = newLeft; + cropRect.right = newLeft + newWidth; + } else { + cropRect.right = newRight; + cropRect.left = newRight - newWidth; + } + + if (top) { + cropRect.top = newTop; + cropRect.bottom = newTop + newHeight; + } else { + cropRect.bottom = newBottom; + cropRect.top = newBottom - newHeight; + } + } + + private int getActiveCorner(float x, float y) { + if (Math.abs(x - cropRect.left) <= touchArea && Math.abs(y - cropRect.top) <= touchArea) { + return CORNER_TOP_LEFT; + } + if (Math.abs(x - cropRect.right) <= touchArea && Math.abs(y - cropRect.top) <= touchArea) { + return CORNER_TOP_RIGHT; + } + if (Math.abs(x - cropRect.left) <= touchArea && Math.abs(y - cropRect.bottom) <= touchArea) { + return CORNER_BOTTOM_LEFT; + } + if (Math.abs(x - cropRect.right) <= touchArea && Math.abs(y - cropRect.bottom) <= touchArea) { + return CORNER_BOTTOM_RIGHT; + } + if (cropRect.contains(x, y)) { + return CORNER_CENTER; + } + return -1; + } + + private float getSpan(MotionEvent event) { + float x0 = event.getX(0); + float y0 = event.getY(0); + float x1 = event.getX(1); + float y1 = event.getY(1); + return (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); } } \ No newline at end of file diff --git a/gallery/src/main/res/layout/activity_crop.xml b/gallery/src/main/res/layout/activity_crop.xml index 73e87c3..45a5925 100644 --- a/gallery/src/main/res/layout/activity_crop.xml +++ b/gallery/src/main/res/layout/activity_crop.xml @@ -41,13 +41,8 @@ - - \ No newline at end of file