实现画布控件背景颜色拾取和剪裁画布功能

- 画布控件创建canvasBitmap保存背景和图片
- 剪裁时剪裁画布控件而非原图片
- 添加颜色拾取按钮和功能
- 可从图片中拾取颜色设置为背景
This commit is contained in:
2026-04-26 19:54:50 +08:00
parent 57c36b09ac
commit b01482470a
4 changed files with 121 additions and 43 deletions

View File

@@ -7,6 +7,7 @@ import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
@@ -55,6 +56,16 @@ public class CropActivity extends AppCompatActivity {
} }
}); });
final ImageView btnColorPick = findViewById(R.id.btn_color_pick);
btnColorPick.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean pickMode = !cropCanvasView.isColorPickMode();
cropCanvasView.setColorPickMode(pickMode);
btnColorPick.setAlpha(pickMode ? 0.5f : 1.0f);
}
});
findViewById(R.id.btn_done).setOnClickListener(new View.OnClickListener() { findViewById(R.id.btn_done).setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -107,36 +118,26 @@ public class CropActivity extends AppCompatActivity {
} }
private void saveCroppedCover() { private void saveCroppedCover() {
if (originalBitmap == null) { Bitmap canvasBitmap = cropCanvasView.getCanvasBitmap();
if (canvasBitmap == null) {
Toast.makeText(this, "Failed to get image", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Failed to get image", Toast.LENGTH_SHORT).show();
return; return;
} }
try { try {
RectF cropRect = cropCanvasView.getCropRect(); RectF cropRect = cropCanvasView.getCropRect();
RectF canvasBounds = cropCanvasView.getCanvasBounds();
RectF imageBounds = cropCanvasView.getImageBounds();
int imgW = originalBitmap.getWidth(); int cropX = (int) cropRect.left;
int imgH = originalBitmap.getHeight(); int cropY = (int) cropRect.top;
int cropW = (int) cropRect.width();
int cropH = (int) cropRect.height();
float scaleX = (float) imgW / canvasBounds.width(); cropX = Math.max(0, Math.min(cropX, canvasBitmap.getWidth() - 1));
float scaleY = (float) imgH / canvasBounds.height(); cropY = Math.max(0, Math.min(cropY, canvasBitmap.getHeight() - 1));
cropW = Math.max(1, Math.min(cropW, canvasBitmap.getWidth() - cropX));
cropH = Math.max(1, Math.min(cropH, canvasBitmap.getHeight() - cropY));
float canvasLeft = cropRect.left - canvasBounds.left; Bitmap cropped = Bitmap.createBitmap(canvasBitmap, cropX, cropY, cropW, cropH);
float canvasTop = cropRect.top - canvasBounds.top;
int cropX = (int) ((canvasLeft - imageBounds.left) * scaleX);
int cropY = (int) ((canvasTop - imageBounds.top) * scaleY);
int cropW = (int) (cropRect.width() * scaleX);
int cropH = (int) (cropRect.height() * scaleY);
cropX = Math.max(0, Math.min(cropX, imgW - 1));
cropY = Math.max(0, Math.min(cropY, imgH - 1));
cropW = Math.max(1, Math.min(cropW, imgW - cropX));
cropH = Math.max(1, Math.min(cropH, imgH - cropY));
Bitmap cropped = Bitmap.createBitmap(originalBitmap, cropX, cropY, cropW, cropH);
File cacheDir = getCacheDir(); File cacheDir = getCacheDir();
File coverFile = new File(cacheDir, "cover_" + System.currentTimeMillis() + ".png"); File coverFile = new File(cacheDir, "cover_" + System.currentTimeMillis() + ".png");

View File

@@ -38,7 +38,11 @@ public class CropCanvasView extends View {
private RectF imageBounds = new RectF(); private RectF imageBounds = new RectF();
private RectF canvasBounds = new RectF(); private RectF canvasBounds = new RectF();
private Bitmap originalBitmap; private Bitmap originalBitmap;
private Bitmap canvasBitmap;
private float initialSpan; private float initialSpan;
private int backgroundColor = Color.GREEN;
private boolean colorPickMode = false;
private float pickX, pickY;
public CropCanvasView(Context context) { public CropCanvasView(Context context) {
super(context); super(context);
@@ -98,10 +102,51 @@ public class CropCanvasView extends View {
cropRect = new RectF(0, 0, canvasWidth, canvasHeight); cropRect = new RectF(0, 0, canvasWidth, canvasHeight);
createCanvasBitmap();
requestLayout(); requestLayout();
invalidate(); invalidate();
} }
private void createCanvasBitmap() {
if (canvasBitmap != null) {
canvasBitmap.recycle();
}
canvasBitmap = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_8888);
android.graphics.Canvas canvas = new android.graphics.Canvas(canvasBitmap);
canvas.drawColor(backgroundColor);
if (originalBitmap != null && !originalBitmap.isRecycled()) {
canvas.drawBitmap(originalBitmap, imageBounds.left, imageBounds.top, imagePaint);
}
}
public Bitmap getCanvasBitmap() {
return canvasBitmap;
}
public void setBackgroundColor(int color) {
this.backgroundColor = color;
createCanvasBitmap();
invalidate();
}
public int getBackgroundColor() {
return backgroundColor;
}
public void setColorPickMode(boolean enable) {
this.colorPickMode = enable;
if (enable) {
pickX = -1;
pickY = -1;
}
invalidate();
}
public boolean isColorPickMode() {
return colorPickMode;
}
public void scaleToView(int viewWidth, int viewHeight) { public void scaleToView(int viewWidth, int viewHeight) {
if (viewWidth > 0 && viewHeight > 0 && canvasWidth > 0 && canvasHeight > 0) { if (viewWidth > 0 && viewHeight > 0 && canvasWidth > 0 && canvasHeight > 0) {
float scale = Math.max((float) viewWidth / canvasWidth, (float) viewHeight / canvasHeight); float scale = Math.max((float) viewWidth / canvasWidth, (float) viewHeight / canvasHeight);
@@ -141,14 +186,6 @@ public class CropCanvasView extends View {
return canvasHeight; return canvasHeight;
} }
public int getExtendWidth() {
return extendWidth;
}
public int getExtendHeight() {
return extendHeight;
}
public RectF getCanvasBounds() { public RectF getCanvasBounds() {
return new RectF(canvasBounds); return new RectF(canvasBounds);
} }
@@ -157,10 +194,6 @@ public class CropCanvasView extends View {
return new RectF(imageBounds); return new RectF(imageBounds);
} }
public float getCoverRatio() {
return coverRatio;
}
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int w = canvasWidth > 0 ? canvasWidth : 0; int w = canvasWidth > 0 ? canvasWidth : 0;
@@ -172,10 +205,8 @@ public class CropCanvasView extends View {
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
super.onDraw(canvas); super.onDraw(canvas);
canvas.drawColor(Color.GREEN); if (canvasBitmap != null && !canvasBitmap.isRecycled()) {
canvas.drawBitmap(canvasBitmap, 0, 0, null);
if (originalBitmap != null && !originalBitmap.isRecycled()) {
canvas.drawBitmap(originalBitmap, imageBounds.left, imageBounds.top, imagePaint);
} }
if (cropRect != null) { if (cropRect != null) {
@@ -222,6 +253,17 @@ public class CropCanvasView extends View {
float x = event.getX(); float x = event.getX();
float y = event.getY(); float y = event.getY();
if (colorPickMode) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
pickX = x;
pickY = y;
int color = getColorAt(x, y);
setBackgroundColor(color);
return true;
}
return true;
}
switch (event.getAction()) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
lastX = x; lastX = x;
@@ -313,19 +355,24 @@ public class CropCanvasView extends View {
} }
private int getActiveCorner(float x, float y) { private int getActiveCorner(float x, float y) {
if (Math.abs(x - cropRect.left) <= touchArea && Math.abs(y - cropRect.top) <= touchArea) { float dx = Math.abs(x - cropRect.left);
float dy = Math.abs(y - cropRect.top);
float dx2 = Math.abs(x - cropRect.right);
float dy2 = Math.abs(y - cropRect.bottom);
if (dx <= touchArea && dy <= touchArea) {
return CORNER_TOP_LEFT; return CORNER_TOP_LEFT;
} }
if (Math.abs(x - cropRect.right) <= touchArea && Math.abs(y - cropRect.top) <= touchArea) { if (dx2 <= touchArea && dy <= touchArea) {
return CORNER_TOP_RIGHT; return CORNER_TOP_RIGHT;
} }
if (Math.abs(x - cropRect.left) <= touchArea && Math.abs(y - cropRect.bottom) <= touchArea) { if (dx <= touchArea && dy2 <= touchArea) {
return CORNER_BOTTOM_LEFT; return CORNER_BOTTOM_LEFT;
} }
if (Math.abs(x - cropRect.right) <= touchArea && Math.abs(y - cropRect.bottom) <= touchArea) { if (dx2 <= touchArea && dy2 <= touchArea) {
return CORNER_BOTTOM_RIGHT; return CORNER_BOTTOM_RIGHT;
} }
if (cropRect.contains(x, y)) { if (x > cropRect.left && x < cropRect.right && y > cropRect.top && y < cropRect.bottom) {
return CORNER_CENTER; return CORNER_CENTER;
} }
return -1; return -1;
@@ -338,4 +385,16 @@ public class CropCanvasView extends View {
float y1 = event.getY(1); float y1 = event.getY(1);
return (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); return (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
} }
public int getColorAt(float x, float y) {
if (canvasBitmap != null && !canvasBitmap.isRecycled()) {
int bitmapX = (int) x;
int bitmapY = (int) y;
if (bitmapX >= 0 && bitmapX < canvasBitmap.getWidth() &&
bitmapY >= 0 && bitmapY < canvasBitmap.getHeight()) {
return canvasBitmap.getPixel(bitmapX, bitmapY);
}
}
return Color.TRANSPARENT;
}
} }

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3,16.25V21h4.75l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,-0.4 0.4,-1.03 0.01,-1.42zM6.92,19H5v-1.92l8.06,-8.06 1.92,1.92L6.92,19z"/>
</vector>

View File

@@ -21,6 +21,14 @@
android:src="@drawable/ic_close" android:src="@drawable/ic_close"
android:background="?attr/selectableItemBackgroundBorderless"/> android:background="?attr/selectableItemBackgroundBorderless"/>
<ImageView
android:id="@+id/btn_color_pick"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/ic_color_pick"
android:background="?attr/selectableItemBackgroundBorderless"/>
<View <View
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"