Compare commits
13 Commits
gallery-v1
...
gallery-v1
| Author | SHA1 | Date | |
|---|---|---|---|
| 139083c22f | |||
| c49e68d7f1 | |||
| b035d461aa | |||
| 474ddcbb3b | |||
| a7e2646eca | |||
| a12577a369 | |||
| 990ca8e27c | |||
| 7f6cd8295c | |||
| ae3a0c6f64 | |||
| e07931fd3b | |||
| 8d62e7df21 | |||
| 1cca476acd | |||
| 31ea5c8fbb |
@@ -109,7 +109,7 @@ dependencies {
|
|||||||
*/
|
*/
|
||||||
// WinBoLL库 nexus.winboll.cc 地址
|
// WinBoLL库 nexus.winboll.cc 地址
|
||||||
api 'cc.winboll.studio:libaes:15.15.9'
|
api 'cc.winboll.studio:libaes:15.15.9'
|
||||||
api 'cc.winboll.studio:libappbase:15.15.19'
|
api 'cc.winboll.studio:libappbase:15.15.20'
|
||||||
|
|
||||||
// WinBoLL备用库 jitpack.io 地址
|
// WinBoLL备用库 jitpack.io 地址
|
||||||
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sun Apr 26 23:52:51 HKT 2026
|
#Tue Apr 28 13:58:55 HKT 2026
|
||||||
stageCount=7
|
stageCount=9
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.0
|
baseVersion=15.0
|
||||||
publishVersion=15.0.6
|
publishVersion=15.0.8
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.0.7
|
baseBetaVersion=15.0.9
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
package cc.winboll.studio.gallery;
|
package cc.winboll.studio.gallery;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
@@ -8,11 +15,16 @@ 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.ImageView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.SeekBar;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import cc.winboll.studio.libappbase.LogUtils;
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
public class CropActivity extends AppCompatActivity {
|
public class CropActivity extends AppCompatActivity {
|
||||||
public static final String TAG = "CropActivity";
|
public static final String TAG = "CropActivity";
|
||||||
@@ -23,6 +35,7 @@ public class CropActivity extends AppCompatActivity {
|
|||||||
public static final String EXTRA_CROP_HEIGHT = "crop_height";
|
public static final String EXTRA_CROP_HEIGHT = "crop_height";
|
||||||
|
|
||||||
private CropCanvasView cropCanvasView;
|
private CropCanvasView cropCanvasView;
|
||||||
|
private ZoomContainerView zoomContainer;
|
||||||
private Bitmap originalBitmap;
|
private Bitmap originalBitmap;
|
||||||
private String imagePath;
|
private String imagePath;
|
||||||
private String albumPath;
|
private String albumPath;
|
||||||
@@ -73,7 +86,64 @@ public class CropActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findViewById(R.id.btn_info).setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
showCropInfoDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
zoomContainer = findViewById(R.id.zoom_container);
|
||||||
|
|
||||||
|
SeekBar seekBarZoom = findViewById(R.id.seekbar_zoom);
|
||||||
|
seekBarZoom.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
|
||||||
|
if (zoomContainer != null && fromUser) {
|
||||||
|
float scale = 0.1f + (progress / 100f) * 4.9f;
|
||||||
|
zoomContainer.setScaleFactor(scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartTrackingTouch(SeekBar seekBar) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopTrackingTouch(SeekBar seekBar) {}
|
||||||
|
});
|
||||||
|
|
||||||
cropCanvasView = findViewById(R.id.crop_canvas_view);
|
cropCanvasView = findViewById(R.id.crop_canvas_view);
|
||||||
|
|
||||||
|
final View colorView = findViewById(R.id.color_view);
|
||||||
|
cropCanvasView.setOnBackgroundColorChangedListener(new CropCanvasView.OnBackgroundColorChangedListener() {
|
||||||
|
@Override
|
||||||
|
public void onBackgroundColorChanged(int color) {
|
||||||
|
colorView.setBackgroundColor(color);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cropCanvasView.setOnColorPickedListener(new CropCanvasView.OnColorPickedListener() {
|
||||||
|
@Override
|
||||||
|
public void onColorPicked(int color) {
|
||||||
|
int pickX = cropCanvasView.getLastPickImageX();
|
||||||
|
int pickY = cropCanvasView.getLastPickImageY();
|
||||||
|
Toast.makeText(CropActivity.this,
|
||||||
|
"颜色已拾取: #" + String.format("%06X", color & 0xFFFFFF) +
|
||||||
|
" (" + pickX + "," + pickY + ")",
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cropCanvasView.setOnColorPickEndListener(new CropCanvasView.OnColorPickEndListener() {
|
||||||
|
@Override
|
||||||
|
public void onColorPickEnd() {
|
||||||
|
cropCanvasView.setColorPickMode(false);
|
||||||
|
btnColorPick.setAlpha(1.0f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
colorView.setBackgroundColor(cropCanvasView.getBackgroundColor());
|
||||||
|
|
||||||
loadImage();
|
loadImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,37 +188,60 @@ public class CropActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void saveCroppedCover() {
|
private void saveCroppedCover() {
|
||||||
Bitmap canvasBitmap = cropCanvasView.getCanvasBitmap();
|
if (originalBitmap == null || originalBitmap.isRecycled()) {
|
||||||
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 {
|
||||||
|
Bitmap canvasBitmap = cropCanvasView.getCanvasBitmap();
|
||||||
|
if (canvasBitmap == null || canvasBitmap.isRecycled()) {
|
||||||
|
Toast.makeText(this, "Failed to get canvas bitmap", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
RectF cropRect = cropCanvasView.getCropRect();
|
RectF cropRect = cropCanvasView.getCropRect();
|
||||||
|
|
||||||
int cropX = (int) cropRect.left;
|
int bmpX = (int) cropRect.left;
|
||||||
int cropY = (int) cropRect.top;
|
int bmpY = (int) cropRect.top;
|
||||||
int cropW = (int) cropRect.width();
|
int bmpW = (int) cropRect.width();
|
||||||
int cropH = (int) cropRect.height();
|
int bmpH = (int) cropRect.height();
|
||||||
|
|
||||||
cropX = Math.max(0, Math.min(cropX, canvasBitmap.getWidth() - 1));
|
bmpX = Math.max(0, Math.min(bmpX, canvasBitmap.getWidth() - 1));
|
||||||
cropY = Math.max(0, Math.min(cropY, canvasBitmap.getHeight() - 1));
|
bmpY = Math.max(0, Math.min(bmpY, canvasBitmap.getHeight() - 1));
|
||||||
cropW = Math.max(1, Math.min(cropW, canvasBitmap.getWidth() - cropX));
|
bmpW = Math.max(1, Math.min(bmpW, canvasBitmap.getWidth() - bmpX));
|
||||||
cropH = Math.max(1, Math.min(cropH, canvasBitmap.getHeight() - cropY));
|
bmpH = Math.max(1, Math.min(bmpH, canvasBitmap.getHeight() - bmpY));
|
||||||
|
|
||||||
Bitmap cropped = Bitmap.createBitmap(canvasBitmap, cropX, cropY, cropW, cropH);
|
LogUtils.d(TAG, "saveCroppedCover: cropRect=" + cropRect);
|
||||||
|
LogUtils.d(TAG, "saveCroppedCover: crop=(" + bmpX + "," + bmpY + "," + bmpW + "," + bmpH + ")");
|
||||||
|
LogUtils.d(TAG, "saveCroppedCover: canvas size=" + canvasBitmap.getWidth() + "x" + canvasBitmap.getHeight());
|
||||||
|
|
||||||
|
Bitmap cropped = Bitmap.createBitmap(canvasBitmap, bmpX, bmpY, bmpW, bmpH);
|
||||||
|
canvasBitmap.recycle();
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "saveCroppedCover: cropped size=" + cropped.getWidth() + "x" + cropped.getHeight());
|
||||||
|
|
||||||
|
File coverDir = new File(getFilesDir(), "covers");
|
||||||
|
if (!coverDir.exists()) {
|
||||||
|
coverDir.mkdirs();
|
||||||
|
}
|
||||||
|
File coverFile = new File(coverDir, "cover_" + System.currentTimeMillis() + ".png");
|
||||||
|
LogUtils.d(TAG, "saveCroppedCover: cover file=" + coverFile.getAbsolutePath());
|
||||||
|
|
||||||
File cacheDir = getCacheDir();
|
|
||||||
File coverFile = new File(cacheDir, "cover_" + System.currentTimeMillis() + ".png");
|
|
||||||
FileOutputStream fos = new FileOutputStream(coverFile);
|
FileOutputStream fos = new FileOutputStream(coverFile);
|
||||||
cropped.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
cropped.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||||
fos.close();
|
fos.close();
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "saveCroppedCover: file exists=" + coverFile.exists() + ", length=" + coverFile.length());
|
||||||
|
|
||||||
cropped.recycle();
|
cropped.recycle();
|
||||||
|
|
||||||
AlbumCoverDbHelper coverDbHelper = AlbumCoverDbHelper.getInstance(this);
|
AlbumCoverDbHelper coverDbHelper = AlbumCoverDbHelper.getInstance(this);
|
||||||
coverDbHelper.setCoverWithCrop(albumPath, imagePath, coverFile.getAbsolutePath());
|
coverDbHelper.setCoverWithCrop(albumPath, imagePath, coverFile.getAbsolutePath());
|
||||||
|
LogUtils.d(TAG, "saveCroppedCover: cover saved to db, albumPath=" + albumPath);
|
||||||
|
|
||||||
|
Intent broadcastIntent = new Intent(Preferences.ACTION_COVER_UPDATED);
|
||||||
|
sendBroadcast(broadcastIntent);
|
||||||
|
|
||||||
Toast.makeText(this, "封面已保存", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "封面已保存", Toast.LENGTH_SHORT).show();
|
||||||
setResult(RESULT_OK);
|
setResult(RESULT_OK);
|
||||||
@@ -159,6 +252,67 @@ public class CropActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showCropInfoDialog() {
|
||||||
|
StringBuilder info = new StringBuilder();
|
||||||
|
info.append("=== 画布信息 ===\n");
|
||||||
|
info.append("画布宽度: ").append(cropCanvasView.getCanvasWidth()).append("px\n");
|
||||||
|
info.append("画布高度: ").append(cropCanvasView.getCanvasHeight()).append("px\n");
|
||||||
|
|
||||||
|
info.append("\n=== 拾取颜色 ===\n");
|
||||||
|
int bgColor = cropCanvasView.getBackgroundColor();
|
||||||
|
info.append("背景颜色: #").append(String.format("%06X", bgColor & 0xFFFFFF)).append("\n");
|
||||||
|
info.append("拾取坐标: ").append(cropCanvasView.getLastPickImageX()).append(",")
|
||||||
|
.append(cropCanvasView.getLastPickImageY()).append("\n");
|
||||||
|
|
||||||
|
info.append("\n=== 裁剪结果 ===\n");
|
||||||
|
RectF cropRect = cropCanvasView.getCropRect();
|
||||||
|
if (cropRect != null) {
|
||||||
|
info.append("裁剪区域: ").append((int)cropRect.left).append(",")
|
||||||
|
.append((int)cropRect.top).append(",")
|
||||||
|
.append((int)cropRect.right).append(",")
|
||||||
|
.append((int)cropRect.bottom).append("\n");
|
||||||
|
info.append("裁剪宽度: ").append((int)cropRect.width()).append("px\n");
|
||||||
|
info.append("裁剪高度: ").append((int)cropRect.height()).append("px\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap canvasBitmap = cropCanvasView.getCanvasBitmap();
|
||||||
|
final Bitmap[] previewHolder = new Bitmap[1];
|
||||||
|
if (canvasBitmap != null && !canvasBitmap.isRecycled() && cropRect != null) {
|
||||||
|
int bmpX = Math.max(0, (int) cropRect.left);
|
||||||
|
int bmpY = Math.max(0, (int) cropRect.top);
|
||||||
|
int bmpW = Math.min((int) cropRect.width(), canvasBitmap.getWidth() - bmpX);
|
||||||
|
int bmpH = Math.min((int) cropRect.height(), canvasBitmap.getHeight() - bmpY);
|
||||||
|
if (bmpW > 0 && bmpH > 0) {
|
||||||
|
previewHolder[0] = Bitmap.createBitmap(canvasBitmap, bmpX, bmpY, bmpW, bmpH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Bitmap previewBitmap = previewHolder[0];
|
||||||
|
|
||||||
|
View dialogView = getLayoutInflater().inflate(R.layout.dialog_crop_info, null);
|
||||||
|
TextView infoText = dialogView.findViewById(R.id.info_text);
|
||||||
|
ImageView previewImage = dialogView.findViewById(R.id.preview_image);
|
||||||
|
infoText.setText(info.toString());
|
||||||
|
if (previewBitmap != null) {
|
||||||
|
previewImage.setImageBitmap(previewBitmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setTitle("裁剪信息")
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton("确定", null)
|
||||||
|
.create();
|
||||||
|
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||||
|
@Override
|
||||||
|
public void onDismiss(DialogInterface dialog) {
|
||||||
|
if (previewBitmap != null && !previewBitmap.isRecycled()) {
|
||||||
|
previewBitmap.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context;
|
|||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Matrix;
|
||||||
import android.graphics.Paint;
|
import android.graphics.Paint;
|
||||||
import android.graphics.RectF;
|
import android.graphics.RectF;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
@@ -11,6 +12,33 @@ import android.view.MotionEvent;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
public class CropCanvasView extends View {
|
public class CropCanvasView extends View {
|
||||||
|
public interface OnBackgroundColorChangedListener {
|
||||||
|
void onBackgroundColorChanged(int color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnColorPickedListener {
|
||||||
|
void onColorPicked(int color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface OnColorPickEndListener {
|
||||||
|
void onColorPickEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnBackgroundColorChangedListener backgroundColorChangedListener;
|
||||||
|
private OnColorPickedListener colorPickedListener;
|
||||||
|
private OnColorPickEndListener colorPickEndListener;
|
||||||
|
|
||||||
|
public void setOnBackgroundColorChangedListener(OnBackgroundColorChangedListener listener) {
|
||||||
|
this.backgroundColorChangedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnColorPickedListener(OnColorPickedListener listener) {
|
||||||
|
this.colorPickedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnColorPickEndListener(OnColorPickEndListener listener) {
|
||||||
|
this.colorPickEndListener = listener;
|
||||||
|
}
|
||||||
private Paint imagePaint;
|
private Paint imagePaint;
|
||||||
private Paint borderPaint;
|
private Paint borderPaint;
|
||||||
private Paint cornerPaint;
|
private Paint cornerPaint;
|
||||||
@@ -39,11 +67,23 @@ public class CropCanvasView extends View {
|
|||||||
private RectF canvasBounds = new RectF();
|
private RectF canvasBounds = new RectF();
|
||||||
private Bitmap originalBitmap;
|
private Bitmap originalBitmap;
|
||||||
private Bitmap canvasBitmap;
|
private Bitmap canvasBitmap;
|
||||||
|
private float bitmapScale = 1.0f;
|
||||||
|
private Bitmap displayBitmap;
|
||||||
|
private RectF initialSpanRect;
|
||||||
private float initialSpan;
|
private float initialSpan;
|
||||||
private int backgroundColor = Color.GREEN;
|
private int backgroundColor = Color.BLUE;
|
||||||
private boolean colorPickMode = false;
|
private boolean colorPickMode = false;
|
||||||
|
private int previewColor = 0;
|
||||||
|
private float lastPickX = 0;
|
||||||
|
private float lastPickY = 0;
|
||||||
private float pickX, pickY;
|
private float pickX, pickY;
|
||||||
|
|
||||||
|
private float displayScale = 1.0f;
|
||||||
|
private float displayOffsetX = 0f;
|
||||||
|
private float displayOffsetY = 0f;
|
||||||
|
private static final int MAX_DISPLAY_SIZE = 2048;
|
||||||
|
private float containerScale = 1.0f;
|
||||||
|
|
||||||
public CropCanvasView(Context context) {
|
public CropCanvasView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
init();
|
init();
|
||||||
@@ -74,7 +114,81 @@ public class CropCanvasView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setImageBitmap(Bitmap bitmap) {
|
public void setImageBitmap(Bitmap bitmap) {
|
||||||
|
if (displayBitmap != null && displayBitmap != originalBitmap) {
|
||||||
|
displayBitmap.recycle();
|
||||||
|
}
|
||||||
this.originalBitmap = bitmap;
|
this.originalBitmap = bitmap;
|
||||||
|
createDisplayBitmap();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createDisplayBitmap() {
|
||||||
|
if (displayBitmap != null && displayBitmap != originalBitmap) {
|
||||||
|
displayBitmap.recycle();
|
||||||
|
displayBitmap = null;
|
||||||
|
}
|
||||||
|
if (originalBitmap == null || originalBitmap.isRecycled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int w = originalBitmap.getWidth();
|
||||||
|
int h = originalBitmap.getHeight();
|
||||||
|
float scale = 1.0f;
|
||||||
|
if (w > MAX_DISPLAY_SIZE || h > MAX_DISPLAY_SIZE) {
|
||||||
|
scale = Math.min((float) MAX_DISPLAY_SIZE / w, (float) MAX_DISPLAY_SIZE / h);
|
||||||
|
int newW = (int) (w * scale);
|
||||||
|
int newH = (int) (h * scale);
|
||||||
|
displayBitmap = Bitmap.createScaledBitmap(originalBitmap, newW, newH, true);
|
||||||
|
} else {
|
||||||
|
displayBitmap = originalBitmap;
|
||||||
|
}
|
||||||
|
displayBitmapScale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float displayBitmapScale = 1.0f;
|
||||||
|
|
||||||
|
public Bitmap getOriginalBitmap() {
|
||||||
|
return originalBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDisplayBitmapScale() {
|
||||||
|
return displayBitmapScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onContainerScaled() {
|
||||||
|
containerScale = 1.0f;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContainerScale(float scale) {
|
||||||
|
if (scale > 0 && scale != containerScale) {
|
||||||
|
containerScale = scale;
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getContainerScale() {
|
||||||
|
return containerScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bitmap getCanvasBitmap() {
|
||||||
|
if (canvasWidth <= 0 || canvasHeight <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Bitmap canvasBmp = Bitmap.createBitmap(canvasWidth, canvasHeight, Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(canvasBmp);
|
||||||
|
canvas.drawColor(backgroundColor);
|
||||||
|
if (displayBitmap != null && !displayBitmap.isRecycled()) {
|
||||||
|
if (displayBitmap == originalBitmap) {
|
||||||
|
canvas.drawBitmap(displayBitmap, imageBounds.left, imageBounds.top, imagePaint);
|
||||||
|
} else {
|
||||||
|
float invScale = 1f / displayBitmapScale;
|
||||||
|
canvas.save();
|
||||||
|
canvas.scale(invScale, invScale);
|
||||||
|
canvas.drawBitmap(displayBitmap, imageBounds.left * displayBitmapScale,
|
||||||
|
imageBounds.top * displayBitmapScale, imagePaint);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return canvasBmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initCanvas(int imgWidth, int imgHeight, float ratio) {
|
public void initCanvas(int imgWidth, int imgHeight, float ratio) {
|
||||||
@@ -100,33 +214,66 @@ public class CropCanvasView extends View {
|
|||||||
imageBounds.set(left, top, left + imageWidth, top + imageHeight);
|
imageBounds.set(left, top, left + imageWidth, top + imageHeight);
|
||||||
canvasBounds.set(0, 0, canvasWidth, canvasHeight);
|
canvasBounds.set(0, 0, canvasWidth, canvasHeight);
|
||||||
|
|
||||||
cropRect = new RectF(0, 0, canvasWidth, canvasHeight);
|
cropRect = new RectF(canvasBounds);
|
||||||
|
|
||||||
createCanvasBitmap();
|
|
||||||
|
|
||||||
requestLayout();
|
requestLayout();
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createCanvasBitmap() {
|
public void getDisplayMatrix(Matrix matrix) {
|
||||||
if (canvasBitmap != null) {
|
if (canvasWidth <= 0 || canvasHeight <= 0 || getWidth() <= 0 || getHeight() <= 0) {
|
||||||
canvasBitmap.recycle();
|
return;
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
matrix.reset();
|
||||||
|
|
||||||
|
float displayScaleX = (float) getWidth() / canvasWidth;
|
||||||
|
float displayScaleY = (float) getHeight() / canvasHeight;
|
||||||
|
displayScale = Math.min(displayScaleX, displayScaleY);
|
||||||
|
|
||||||
|
displayOffsetX = (getWidth() - canvasWidth * displayScale) / 2f;
|
||||||
|
displayOffsetY = (getHeight() - canvasHeight * displayScale) / 2f;
|
||||||
|
|
||||||
|
matrix.postTranslate(displayOffsetX, displayOffsetY);
|
||||||
|
matrix.postScale(displayScale, displayScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap getCanvasBitmap() {
|
public float screenToImageX(float screenX) {
|
||||||
return canvasBitmap;
|
return (screenX - displayOffsetX) / displayScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float screenToImageY(float screenY) {
|
||||||
|
return (screenY - displayOffsetY) / displayScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float imageToScreenX(float imageX) {
|
||||||
|
return imageX * displayScale + displayOffsetX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float imageToScreenY(float imageY) {
|
||||||
|
return imageY * displayScale + displayOffsetY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getImageColorAt(float screenX, float screenY) {
|
||||||
|
Bitmap bmp = (displayBitmap != null) ? displayBitmap : originalBitmap;
|
||||||
|
if (bmp == null || bmp.isRecycled()) {
|
||||||
|
return Color.TRANSPARENT;
|
||||||
|
}
|
||||||
|
float imgX = screenToImageX(screenX);
|
||||||
|
float imgY = screenToImageY(screenY);
|
||||||
|
int bmpX = (int) ((imgX - imageBounds.left) * displayBitmapScale);
|
||||||
|
int bmpY = (int) ((imgY - imageBounds.top) * displayBitmapScale);
|
||||||
|
if (bmpX >= 0 && bmpX < bmp.getWidth() &&
|
||||||
|
bmpY >= 0 && bmpY < bmp.getHeight()) {
|
||||||
|
return bmp.getPixel(bmpX, bmpY);
|
||||||
|
}
|
||||||
|
return Color.TRANSPARENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBackgroundColor(int color) {
|
public void setBackgroundColor(int color) {
|
||||||
this.backgroundColor = color;
|
this.backgroundColor = color;
|
||||||
createCanvasBitmap();
|
if (backgroundColorChangedListener != null) {
|
||||||
|
backgroundColorChangedListener.onBackgroundColorChanged(color);
|
||||||
|
}
|
||||||
invalidate();
|
invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +281,40 @@ public class CropCanvasView extends View {
|
|||||||
return backgroundColor;
|
return backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPreviewColor() {
|
||||||
|
return previewColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLastPickX() {
|
||||||
|
return lastPickX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getLastPickY() {
|
||||||
|
return lastPickY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastPickImageX() {
|
||||||
|
if (originalBitmap == null || originalBitmap.isRecycled()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
float imgX = screenToImageX(lastPickX);
|
||||||
|
float imgY = screenToImageY(lastPickY);
|
||||||
|
int bmpX = (int) ((imgX - imageBounds.left) * displayBitmapScale);
|
||||||
|
int bmpY = (int) ((imgY - imageBounds.top) * displayBitmapScale);
|
||||||
|
return Math.max(0, Math.min(bmpX, originalBitmap.getWidth() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLastPickImageY() {
|
||||||
|
if (originalBitmap == null || originalBitmap.isRecycled()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
float imgX = screenToImageX(lastPickX);
|
||||||
|
float imgY = screenToImageY(lastPickY);
|
||||||
|
int bmpX = (int) ((imgX - imageBounds.left) * displayBitmapScale);
|
||||||
|
int bmpY = (int) ((imgY - imageBounds.top) * displayBitmapScale);
|
||||||
|
return Math.max(0, Math.min(bmpY, originalBitmap.getHeight() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
public void setColorPickMode(boolean enable) {
|
public void setColorPickMode(boolean enable) {
|
||||||
this.colorPickMode = enable;
|
this.colorPickMode = enable;
|
||||||
if (enable) {
|
if (enable) {
|
||||||
@@ -194,6 +375,14 @@ public class CropCanvasView extends View {
|
|||||||
return new RectF(imageBounds);
|
return new RectF(imageBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getBitmapScale() {
|
||||||
|
return displayScale;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getDisplayScale() {
|
||||||
|
return displayScale;
|
||||||
|
}
|
||||||
|
|
||||||
@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;
|
||||||
@@ -201,21 +390,75 @@ public class CropCanvasView extends View {
|
|||||||
setMeasuredDimension(w, h);
|
setMeasuredDimension(w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||||
|
super.onSizeChanged(w, h, oldw, oldh);
|
||||||
|
getDisplayMatrix(new Matrix());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDraw(Canvas canvas) {
|
protected void onDraw(Canvas canvas) {
|
||||||
super.onDraw(canvas);
|
super.onDraw(canvas);
|
||||||
|
|
||||||
if (canvasBitmap != null && !canvasBitmap.isRecycled()) {
|
if (cropRect == null) return;
|
||||||
canvas.drawBitmap(canvasBitmap, 0, 0, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cropRect != null) {
|
if (displayBitmap != null && !displayBitmap.isRecycled()) {
|
||||||
canvas.drawRect(cropRect, borderPaint);
|
canvas.save();
|
||||||
|
|
||||||
canvas.drawCircle(cropRect.left, cropRect.top, 12, cornerPaint);
|
Matrix matrix = new Matrix();
|
||||||
canvas.drawCircle(cropRect.right, cropRect.top, 12, cornerPaint);
|
getDisplayMatrix(matrix);
|
||||||
canvas.drawCircle(cropRect.left, cropRect.bottom, 12, cornerPaint);
|
canvas.concat(matrix);
|
||||||
canvas.drawCircle(cropRect.right, cropRect.bottom, 12, cornerPaint);
|
|
||||||
|
canvas.drawColor(backgroundColor);
|
||||||
|
|
||||||
|
if (displayBitmap == originalBitmap) {
|
||||||
|
canvas.drawBitmap(displayBitmap, imageBounds.left, imageBounds.top, imagePaint);
|
||||||
|
} else {
|
||||||
|
float invScale = 1f / displayBitmapScale;
|
||||||
|
canvas.save();
|
||||||
|
canvas.scale(invScale, invScale);
|
||||||
|
canvas.drawBitmap(displayBitmap, imageBounds.left * displayBitmapScale,
|
||||||
|
imageBounds.top * displayBitmapScale, imagePaint);
|
||||||
|
canvas.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.restore();
|
||||||
|
|
||||||
|
if (containerScale != 1.0f) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.scale(containerScale, containerScale);
|
||||||
|
canvas.drawRect(cropRect, borderPaint);
|
||||||
|
float cornerRadius = 12f / containerScale;
|
||||||
|
canvas.drawCircle(cropRect.left, cropRect.top, cornerRadius, cornerPaint);
|
||||||
|
canvas.drawCircle(cropRect.right, cropRect.top, cornerRadius, cornerPaint);
|
||||||
|
canvas.drawCircle(cropRect.left, cropRect.bottom, cornerRadius, cornerPaint);
|
||||||
|
canvas.drawCircle(cropRect.right, cropRect.bottom, cornerRadius, cornerPaint);
|
||||||
|
canvas.restore();
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (containerScale != 1.0f) {
|
||||||
|
canvas.save();
|
||||||
|
canvas.scale(containerScale, containerScale);
|
||||||
|
canvas.drawRect(cropRect, borderPaint);
|
||||||
|
float cornerRadius = 12f / containerScale;
|
||||||
|
canvas.drawCircle(cropRect.left, cropRect.top, cornerRadius, cornerPaint);
|
||||||
|
canvas.drawCircle(cropRect.right, cropRect.top, cornerRadius, cornerPaint);
|
||||||
|
canvas.drawCircle(cropRect.left, cropRect.bottom, cornerRadius, cornerPaint);
|
||||||
|
canvas.drawCircle(cropRect.right, cropRect.bottom, cornerRadius, cornerPaint);
|
||||||
|
canvas.restore();
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,8 +483,15 @@ public class CropCanvasView extends View {
|
|||||||
newWidth = Math.max(minSize, Math.min(newWidth, canvasBounds.width()));
|
newWidth = Math.max(minSize, Math.min(newWidth, canvasBounds.width()));
|
||||||
newHeight = newWidth / coverRatio;
|
newHeight = newWidth / coverRatio;
|
||||||
|
|
||||||
cropRect.set(centerX - newWidth / 2, centerY - newHeight / 2,
|
float newLeft = centerX - newWidth / 2;
|
||||||
centerX + newWidth / 2, centerY + newHeight / 2);
|
float newTop = centerY - newHeight / 2;
|
||||||
|
float newRight = centerX + newWidth / 2;
|
||||||
|
float newBottom = centerY + newHeight / 2;
|
||||||
|
|
||||||
|
if (newLeft >= canvasBounds.left && newRight <= canvasBounds.right &&
|
||||||
|
newTop >= canvasBounds.top && newBottom <= canvasBounds.bottom) {
|
||||||
|
cropRect.set(newLeft, newTop, newRight, newBottom);
|
||||||
|
}
|
||||||
|
|
||||||
initialSpan = span;
|
initialSpan = span;
|
||||||
invalidate();
|
invalidate();
|
||||||
@@ -253,12 +503,42 @@ public class CropCanvasView extends View {
|
|||||||
float x = event.getX();
|
float x = event.getX();
|
||||||
float y = event.getY();
|
float y = event.getY();
|
||||||
|
|
||||||
|
RectF bounds = canvasBounds;
|
||||||
|
if (containerScale != 1.0f) {
|
||||||
|
x = x / containerScale;
|
||||||
|
y = y / containerScale;
|
||||||
|
bounds = new RectF(
|
||||||
|
canvasBounds.left,
|
||||||
|
canvasBounds.top,
|
||||||
|
canvasBounds.right,
|
||||||
|
canvasBounds.bottom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (colorPickMode) {
|
if (colorPickMode) {
|
||||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||||
pickX = x;
|
if (originalBitmap != null && !originalBitmap.isRecycled()) {
|
||||||
pickY = y;
|
previewColor = getImageColorAt(x, y);
|
||||||
int color = getColorAt(x, y);
|
lastPickX = x;
|
||||||
setBackgroundColor(color);
|
lastPickY = y;
|
||||||
|
if (backgroundColorChangedListener != null) {
|
||||||
|
backgroundColorChangedListener.onBackgroundColorChanged(previewColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||||
|
if (previewColor != 0 && previewColor != Color.TRANSPARENT) {
|
||||||
|
backgroundColor = previewColor;
|
||||||
|
if (colorPickedListener != null) {
|
||||||
|
colorPickedListener.onColorPicked(backgroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (colorPickEndListener != null) {
|
||||||
|
colorPickEndListener.onColorPickEnd();
|
||||||
|
}
|
||||||
|
previewColor = 0;
|
||||||
|
invalidate();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -281,11 +561,11 @@ public class CropCanvasView extends View {
|
|||||||
float newRight = cropRect.right + dx;
|
float newRight = cropRect.right + dx;
|
||||||
float newBottom = cropRect.bottom + dy;
|
float newBottom = cropRect.bottom + dy;
|
||||||
|
|
||||||
if (newLeft >= canvasBounds.left && newRight <= canvasBounds.right) {
|
if (newLeft >= bounds.left && newRight <= bounds.right) {
|
||||||
cropRect.left = newLeft;
|
cropRect.left = newLeft;
|
||||||
cropRect.right = newRight;
|
cropRect.right = newRight;
|
||||||
}
|
}
|
||||||
if (newTop >= canvasBounds.top && newBottom <= canvasBounds.bottom) {
|
if (newTop >= bounds.top && newBottom <= bounds.bottom) {
|
||||||
cropRect.top = newTop;
|
cropRect.top = newTop;
|
||||||
cropRect.bottom = newBottom;
|
cropRect.bottom = newBottom;
|
||||||
}
|
}
|
||||||
@@ -387,14 +667,6 @@ public class CropCanvasView extends View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getColorAt(float x, float y) {
|
public int getColorAt(float x, float y) {
|
||||||
if (canvasBitmap != null && !canvasBitmap.isRecycled()) {
|
return getImageColorAt(x, y);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
package cc.winboll.studio.gallery;
|
package cc.winboll.studio.gallery;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.media.MediaScannerConnection;
|
import android.media.MediaScannerConnection;
|
||||||
@@ -192,13 +195,16 @@ private void loadAlbums() {
|
|||||||
LogUtils.d(TAG, "scanning folder: " + subfolder.getName());
|
LogUtils.d(TAG, "scanning folder: " + subfolder.getName());
|
||||||
String albumPath = subfolder.getAbsolutePath();
|
String albumPath = subfolder.getAbsolutePath();
|
||||||
String coverPath = coverDbHelper.getCover(albumPath);
|
String coverPath = coverDbHelper.getCover(albumPath);
|
||||||
|
LogUtils.d(TAG, "loadAlbums: album=" + albumPath + ", coverPath=" + coverPath);
|
||||||
Uri coverUri = null;
|
Uri coverUri = null;
|
||||||
if (coverPath != null) {
|
if (coverPath != null) {
|
||||||
File coverFile = new File(coverPath);
|
File coverFile = new File(coverPath);
|
||||||
if (coverFile.exists()) {
|
if (coverFile.exists()) {
|
||||||
coverUri = Uri.fromFile(coverFile);
|
coverUri = Uri.fromFile(coverFile);
|
||||||
|
LogUtils.d(TAG, "loadAlbums: cover from file=" + coverFile.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
coverUri = getUriFromPath(coverPath);
|
coverUri = getUriFromPath(coverPath);
|
||||||
|
LogUtils.d(TAG, "loadAlbums: cover from media store path=" + coverPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (coverUri == null) {
|
if (coverUri == null) {
|
||||||
@@ -299,9 +305,19 @@ private void loadAlbums() {
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BroadcastReceiver coverUpdatedReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (Preferences.ACTION_COVER_UPDATED.equals(intent.getAction())) {
|
||||||
|
loadAlbums();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
registerReceiver(coverUpdatedReceiver, new IntentFilter(Preferences.ACTION_COVER_UPDATED));
|
||||||
if (checkPermission()) {
|
if (checkPermission()) {
|
||||||
scanMediaStore();
|
scanMediaStore();
|
||||||
loadAlbums();
|
loadAlbums();
|
||||||
@@ -313,6 +329,12 @@ private void loadAlbums() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
unregisterReceiver(coverUpdatedReceiver);
|
||||||
|
}
|
||||||
|
|
||||||
private void scanMediaStore() {
|
private void scanMediaStore() {
|
||||||
String folderPath = prefs.getFolderPath();
|
String folderPath = prefs.getFolderPath();
|
||||||
File baseFolder = new File(folderPath);
|
File baseFolder = new File(folderPath);
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ public class Preferences {
|
|||||||
private static final String KEY_COVER_WIDTH = "cover_width";
|
private static final String KEY_COVER_WIDTH = "cover_width";
|
||||||
private static final String KEY_COVER_HEIGHT = "cover_height";
|
private static final String KEY_COVER_HEIGHT = "cover_height";
|
||||||
private static final String KEY_COVER_RATIO = "cover_ratio";
|
private static final String KEY_COVER_RATIO = "cover_ratio";
|
||||||
|
|
||||||
|
public static final String ACTION_COVER_UPDATED = "cc.winboll.studio.gallery.COVER_UPDATED";
|
||||||
|
|
||||||
private static final int DEFAULT_BG_TYPE = 0;
|
private static final int DEFAULT_BG_TYPE = 0;
|
||||||
private static final int DEFAULT_SORT_MODE = 0;
|
private static final int DEFAULT_SORT_MODE = 0;
|
||||||
private static final int DEFAULT_COVER_WIDTH = 240;
|
private static final int DEFAULT_COVER_WIDTH = 240;
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package cc.winboll.studio.gallery;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.ScaleGestureDetector;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
public class ZoomContainerView extends FrameLayout {
|
||||||
|
private float scaleFactor = 1.0f;
|
||||||
|
private float minScale = 0.1f;
|
||||||
|
private float maxScale = 5.0f;
|
||||||
|
private static final float ZOOM_STEP = 0.25f;
|
||||||
|
|
||||||
|
private ScaleGestureDetector scaleGestureDetector;
|
||||||
|
private GestureDetector gestureDetector;
|
||||||
|
private Paint borderPaint;
|
||||||
|
|
||||||
|
public ZoomContainerView(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoomContainerView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ZoomContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setWillNotDraw(false);
|
||||||
|
setBackgroundColor(Color.YELLOW);
|
||||||
|
|
||||||
|
borderPaint = new Paint();
|
||||||
|
borderPaint.setColor(Color.parseColor("#333333"));
|
||||||
|
borderPaint.setStyle(Paint.Style.STROKE);
|
||||||
|
borderPaint.setStrokeWidth(2);
|
||||||
|
|
||||||
|
scaleGestureDetector = new ScaleGestureDetector(getContext(), new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onScale(ScaleGestureDetector detector) {
|
||||||
|
scaleFactor *= detector.getScaleFactor();
|
||||||
|
scaleFactor = Math.max(minScale, Math.min(scaleFactor, maxScale));
|
||||||
|
invalidate();
|
||||||
|
requestLayout();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
gestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onDoubleTap(MotionEvent e) {
|
||||||
|
if (scaleFactor > 1.5f) {
|
||||||
|
scaleFactor = 1.0f;
|
||||||
|
} else {
|
||||||
|
scaleFactor = 2.0f;
|
||||||
|
}
|
||||||
|
invalidate();
|
||||||
|
requestLayout();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void zoomIn() {
|
||||||
|
scaleFactor = Math.min(maxScale, scaleFactor + ZOOM_STEP);
|
||||||
|
requestMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void zoomOut() {
|
||||||
|
scaleFactor = Math.max(minScale, scaleFactor - ZOOM_STEP);
|
||||||
|
requestMeasure();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestMeasure() {
|
||||||
|
removeCallbacks(measureRunable);
|
||||||
|
post(measureRunable);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Runnable measureRunable = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
requestLayout();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public float getScaleFactor() {
|
||||||
|
return scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleFactor(float scale) {
|
||||||
|
scaleFactor = Math.max(minScale, Math.min(scale, maxScale));
|
||||||
|
int childCount = getChildCount();
|
||||||
|
if (childCount > 0) {
|
||||||
|
View child = getChildAt(0);
|
||||||
|
if (child instanceof CropCanvasView) {
|
||||||
|
((CropCanvasView) child).setContainerScale(scaleFactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestLayout();
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetZoom() {
|
||||||
|
scaleFactor = 1.0f;
|
||||||
|
invalidate();
|
||||||
|
requestLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
|
int childCount = getChildCount();
|
||||||
|
if (childCount > 0) {
|
||||||
|
View child = getChildAt(0);
|
||||||
|
child.measure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
int childW = child.getMeasuredWidth();
|
||||||
|
int childH = child.getMeasuredHeight();
|
||||||
|
int scaledW = (int) (childW * scaleFactor);
|
||||||
|
int scaledH = (int) (childH * scaleFactor);
|
||||||
|
int widthSpec = MeasureSpec.makeMeasureSpec(scaledW, MeasureSpec.EXACTLY);
|
||||||
|
int heightSpec = MeasureSpec.makeMeasureSpec(scaledH, MeasureSpec.EXACTLY);
|
||||||
|
child.measure(widthSpec, heightSpec);
|
||||||
|
setMeasuredDimension(scaledW, scaledH);
|
||||||
|
} else {
|
||||||
|
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
|
int childCount = getChildCount();
|
||||||
|
if (childCount > 0) {
|
||||||
|
View child = getChildAt(0);
|
||||||
|
int childW = child.getMeasuredWidth();
|
||||||
|
int childH = child.getMeasuredHeight();
|
||||||
|
int scaledW = (int) (childW * scaleFactor);
|
||||||
|
int scaledH = (int) (childH * scaleFactor);
|
||||||
|
int parentW = right - left;
|
||||||
|
int parentH = bottom - top;
|
||||||
|
int childLeft = (parentW - scaledW) / 2;
|
||||||
|
int childTop = (parentH - scaledH) / 2;
|
||||||
|
child.layout(childLeft, childTop, childLeft + scaledW, childTop + scaledH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDraw(Canvas canvas) {
|
||||||
|
super.onDraw(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
boolean handled = scaleGestureDetector.onTouchEvent(event);
|
||||||
|
if (!scaleGestureDetector.isInProgress()) {
|
||||||
|
handled = gestureDetector.onTouchEvent(event) || handled;
|
||||||
|
}
|
||||||
|
return handled || super.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
gallery/src/main/res/drawable/bg_color_circle.xml
Normal file
6
gallery/src/main/res/drawable/bg_color_circle.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="@android:color/holo_green_dark"/>
|
||||||
|
<size android:width="32dp" android:height="32dp"/>
|
||||||
|
</shape>
|
||||||
6
gallery/src/main/res/drawable/bg_color_circle_border.xml
Normal file
6
gallery/src/main/res/drawable/bg_color_circle_border.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
<stroke android:width="2dp" android:color="@android:color/white"/>
|
||||||
|
<size android:width="36dp" android:height="36dp"/>
|
||||||
|
</shape>
|
||||||
6
gallery/src/main/res/drawable/bg_dialog.xml
Normal file
6
gallery/src/main/res/drawable/bg_dialog.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/black"/>
|
||||||
|
<stroke android:width="2dp" android:color="@android:color/white"/>
|
||||||
|
</shape>
|
||||||
9
gallery/src/main/res/drawable/ic_done.xml
Normal file
9
gallery/src/main/res/drawable/ic_done.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<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="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||||
|
</vector>
|
||||||
9
gallery/src/main/res/drawable/ic_zoom_in.xml
Normal file
9
gallery/src/main/res/drawable/ic_zoom_in.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<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="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||||
|
</vector>
|
||||||
9
gallery/src/main/res/drawable/ic_zoom_out.xml
Normal file
9
gallery/src/main/res/drawable/ic_zoom_out.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<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="M19,13H5v-2h14v2z"/>
|
||||||
|
</vector>
|
||||||
@@ -21,6 +21,27 @@
|
|||||||
android:src="@drawable/ic_close"
|
android:src="@drawable/ic_close"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/color_icon_container"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_marginStart="8dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/color_view"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_color_circle"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_color_circle_border"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/btn_color_pick"
|
android:id="@+id/btn_color_pick"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
@@ -34,23 +55,82 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"/>
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
<TextView
|
<ImageView
|
||||||
android:id="@+id/btn_done"
|
android:id="@+id/btn_info"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:text="完成"
|
android:padding="12dp"
|
||||||
android:textColor="@android:color/white"
|
android:src="@drawable/ic_info"
|
||||||
android:textSize="16sp"
|
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||||
android:gravity="center"
|
|
||||||
android:paddingHorizontal="16dp"
|
<ImageView
|
||||||
|
android:id="@+id/btn_done"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:src="@drawable/ic_done"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<cc.winboll.studio.gallery.CropCanvasView
|
<ScrollView
|
||||||
android:id="@+id/crop_canvas_view"
|
android:id="@+id/scroll_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"/>
|
android:layout_marginTop="56dp"
|
||||||
|
android:layout_marginBottom="56dp"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<cc.winboll.studio.gallery.ZoomContainerView
|
||||||
|
android:id="@+id/zoom_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center">
|
||||||
|
|
||||||
|
<cc.winboll.studio.gallery.CropCanvasView
|
||||||
|
android:id="@+id/crop_canvas_view"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"/>
|
||||||
|
|
||||||
|
</cc.winboll.studio.gallery.ZoomContainerView>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_gravity="bottom"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@color/colorPrimary"
|
||||||
|
android:paddingHorizontal="16dp"
|
||||||
|
android:layout_marginBottom="0dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="缩小"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
<SeekBar
|
||||||
|
android:id="@+id/seekbar_zoom"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:min="0"
|
||||||
|
android:max="100"
|
||||||
|
android:progress="50"
|
||||||
|
android:layout_marginHorizontal="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="放大"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
26
gallery/src/main/res/layout/dialog_crop_info.xml
Normal file
26
gallery/src/main/res/layout/dialog_crop_info.xml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:background="@drawable/bg_dialog">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/info_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textColor="@android:color/white"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/preview_image"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="200dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:background="@android:color/black"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
20
gallery/src/main/res/layout/item_color_icon.xml
Normal file
20
gallery/src/main/res/layout/item_color_icon.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/color_view"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_color_circle"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@drawable/bg_color_circle_border"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
Reference in New Issue
Block a user