Compare commits
35 Commits
gallery-v1
...
gallery-v1
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c0e56462e | |||
| 4d977646e6 | |||
| 2d912abf92 | |||
| f764de4077 | |||
| 0788a52652 | |||
| ffea383a4e | |||
| 9c1e08a88b | |||
| abb28b766a | |||
| b6cd91c298 | |||
| 61c2bab83e | |||
| 5a700a0808 | |||
| dad179c15f | |||
| 1b62ff288f | |||
| 87ad6668a4 | |||
| 139083c22f | |||
| c49e68d7f1 | |||
| b035d461aa | |||
| 474ddcbb3b | |||
| a7e2646eca | |||
| a12577a369 | |||
| 990ca8e27c | |||
| 7f6cd8295c | |||
| ae3a0c6f64 | |||
| e07931fd3b | |||
| 8d62e7df21 | |||
| 1cca476acd | |||
| 31ea5c8fbb | |||
| e62a907378 | |||
| c85ba8324b | |||
| 3e49c33bc1 | |||
| 3aab93cc4d | |||
| 90d8330798 | |||
| b01482470a | |||
| 57c36b09ac | |||
| b5d68dfd1e |
@@ -109,7 +109,7 @@ dependencies {
|
||||
*/
|
||||
// WinBoLL库 nexus.winboll.cc 地址
|
||||
api 'cc.winboll.studio:libaes:15.15.9'
|
||||
api 'cc.winboll.studio:libappbase:15.15.19'
|
||||
api 'cc.winboll.studio:libappbase:15.15.21'
|
||||
|
||||
// WinBoLL备用库 jitpack.io 地址
|
||||
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Apr 26 11:32:37 HKT 2026
|
||||
stageCount=6
|
||||
#Fri May 01 04:09:20 HKT 2026
|
||||
stageCount=12
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.5
|
||||
publishVersion=15.0.11
|
||||
buildCount=0
|
||||
baseBetaVersion=15.0.6
|
||||
baseBetaVersion=15.0.12
|
||||
|
||||
@@ -46,6 +46,14 @@
|
||||
<activity
|
||||
android:name=".TrashActivity"
|
||||
android:label="@string/trash"/>
|
||||
|
||||
<activity
|
||||
android:name=".CropActivity"
|
||||
android:label="调整封面"/>
|
||||
|
||||
<activity
|
||||
android:name=".AboutActivity"
|
||||
android:label="@string/about"/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.models.APPInfo;
|
||||
import cc.winboll.studio.libappbase.views.AboutView;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity {
|
||||
|
||||
public static final String TAG = "AboutActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_about);
|
||||
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
AboutView aboutView = findViewById(R.id.aboutview);
|
||||
aboutView.setAPPInfo(genDefaultAppInfo());
|
||||
}
|
||||
|
||||
private APPInfo genDefaultAppInfo() {
|
||||
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||
String branchName = "gallery";
|
||||
APPInfo appInfo = new APPInfo();
|
||||
appInfo.setAppName("Gallery");
|
||||
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||
appInfo.setAppDescription(getString(R.string.app_description));
|
||||
appInfo.setAppGitName("WinBoLL");
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(branchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=Gallery");
|
||||
appInfo.setAppAPKName("Gallery");
|
||||
appInfo.setAppAPKFolderName("Gallery");
|
||||
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
|
||||
return appInfo;
|
||||
}
|
||||
}
|
||||
@@ -240,4 +240,12 @@ private String getSortOrder(int sortMode) {
|
||||
adapter.refreshBg();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == 100 && resultCode == RESULT_OK) {
|
||||
loadImages();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
private Preferences prefs;
|
||||
private int bgType = 0;
|
||||
private PinnedAlbumDbHelper pinnedDbHelper;
|
||||
private OnCoverSizeListener coverSizeListener;
|
||||
private boolean coverSizeReported = false;
|
||||
|
||||
private int getBgRes() {
|
||||
switch (bgType) {
|
||||
@@ -43,10 +45,21 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
public interface OnAlbumClickListener {
|
||||
void onAlbumClick(Album album);
|
||||
}
|
||||
|
||||
|
||||
public interface OnCoverSizeListener {
|
||||
void onCoverSize(int width, int height);
|
||||
}
|
||||
|
||||
public static final int DEFAULT_COVER_WIDTH = 240;
|
||||
public static final int DEFAULT_COVER_HEIGHT = 120;
|
||||
|
||||
public void setOnAlbumClickListener(OnAlbumClickListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public void setOnCoverSizeListener(OnCoverSizeListener listener) {
|
||||
this.coverSizeListener = listener;
|
||||
}
|
||||
|
||||
public void setData(ArrayList<Album> albums) {
|
||||
this.albums = sortAlbums(albums);
|
||||
@@ -84,6 +97,10 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void refreshCover() {
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void refreshPinned() {
|
||||
if (pinnedDbHelper != null && albums != null && !albums.isEmpty()) {
|
||||
ArrayList<Album> pinned = new ArrayList<>();
|
||||
@@ -113,7 +130,7 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
private void showContextMenu(View view, final Album album) {
|
||||
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(view.getContext());
|
||||
builder.setTitle(album.getName());
|
||||
boolean isPinned = pinnedDbHelper != null && pinnedDbHelper.isPinned(album.getPath());
|
||||
final boolean isPinned = pinnedDbHelper != null && pinnedDbHelper.isPinned(album.getPath());
|
||||
String[] items = isPinned ? new String[]{"取消置顶"} : new String[]{"置顶"};
|
||||
builder.setItems(items, new android.content.DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
@@ -162,6 +179,29 @@ private void showContextMenu(View view, final Album album) {
|
||||
holder.albumName.setText(album.getName());
|
||||
holder.imageCount.setText(album.getImageCount() + " photos");
|
||||
|
||||
holder.itemView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!coverSizeReported && prefs != null) {
|
||||
int savedWidth = prefs.getCoverWidth();
|
||||
if (savedWidth == AlbumAdapter.DEFAULT_COVER_WIDTH) {
|
||||
int width = holder.coverImage.getWidth();
|
||||
int height = holder.coverImage.getHeight();
|
||||
if (width > 0 && height > 0) {
|
||||
prefs.setCoverWidth(width);
|
||||
prefs.setCoverHeight(height);
|
||||
float ratio = (float) width / height;
|
||||
prefs.setCoverRatio(ratio);
|
||||
coverSizeReported = true;
|
||||
if (coverSizeListener != null) {
|
||||
coverSizeListener.onCoverSize(width, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
holder.itemView.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -176,37 +216,22 @@ private void showContextMenu(View view, final Album album) {
|
||||
String uriString = coverUri.toString();
|
||||
LogUtils.d(TAG, "uri scheme: " + coverUri.getScheme() + ", path: " + uriString);
|
||||
|
||||
// For content:// URIs, try to get the actual file path
|
||||
if ("content".equals(coverUri.getScheme())) {
|
||||
try {
|
||||
android.database.Cursor cursor = holder.coverImage.getContext().getContentResolver()
|
||||
.query(coverUri, new String[]{MediaStore.Images.Media.DATA}, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
String filePath = cursor.getString(dataColumn);
|
||||
cursor.close();
|
||||
if (filePath != null) {
|
||||
File actualFile = new File(filePath);
|
||||
LogUtils.d(TAG, "actual file: " + actualFile.getAbsolutePath() + ", exists=" + actualFile.exists());
|
||||
// Use file path instead of content URI for better compatibility
|
||||
Glide.with(holder.coverImage.getContext())
|
||||
.load(actualFile)
|
||||
.centerCrop()
|
||||
.into(holder.coverImage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "query error: " + e.getMessage());
|
||||
if ("file".equals(coverUri.getScheme())) {
|
||||
File coverFile = new File(coverUri.getPath());
|
||||
if (coverFile.exists()) {
|
||||
Glide.with(holder.coverImage.getContext())
|
||||
.load(coverFile)
|
||||
.fitCenter()
|
||||
.into(holder.coverImage);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Glide.with(holder.coverImage.getContext())
|
||||
.load(coverUri)
|
||||
.fitCenter()
|
||||
.into(holder.coverImage);
|
||||
}
|
||||
|
||||
// Fallback to content URI
|
||||
Glide.with(holder.coverImage.getContext())
|
||||
.load(coverUri)
|
||||
.centerCrop()
|
||||
.into(holder.coverImage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -227,4 +252,4 @@ private void showContextMenu(View view, final Album album) {
|
||||
imageCount = itemView.findViewById(R.id.image_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,14 +10,16 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
public class AlbumCoverDbHelper extends SQLiteOpenHelper {
|
||||
public static final String TAG = "AlbumCoverDbHelper";
|
||||
private static final String DB_NAME = "album_cover.db";
|
||||
private static final int DB_VERSION = 1;
|
||||
private static final int DB_VERSION = 2;
|
||||
private static final String TABLE_NAME = "album_covers";
|
||||
private static final String COLUMN_ALBUM_PATH = "album_path";
|
||||
private static final String COLUMN_IMAGE_PATH = "image_path";
|
||||
private static final String COLUMN_CROP_PATH = "crop_path";
|
||||
|
||||
private static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||
+ COLUMN_ALBUM_PATH + " TEXT PRIMARY KEY, "
|
||||
+ COLUMN_IMAGE_PATH + " TEXT)";
|
||||
+ COLUMN_IMAGE_PATH + " TEXT, "
|
||||
+ COLUMN_CROP_PATH + " TEXT)";
|
||||
|
||||
private static AlbumCoverDbHelper dbHelper;
|
||||
|
||||
@@ -39,8 +41,24 @@ public class AlbumCoverDbHelper extends SQLiteOpenHelper {
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
onCreate(db);
|
||||
if (oldVersion < 2) {
|
||||
try {
|
||||
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN " + COLUMN_CROP_PATH + " TEXT");
|
||||
} catch (Exception e) {
|
||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||
onCreate(db);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setCoverWithCrop(String albumPath, String imagePath, String cropPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_ALBUM_PATH, albumPath);
|
||||
values.put(COLUMN_IMAGE_PATH, imagePath);
|
||||
values.put(COLUMN_CROP_PATH, cropPath);
|
||||
db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE);
|
||||
LogUtils.d(TAG, "setCoverWithCrop: album=" + albumPath + ", image=" + imagePath + ", crop=" + cropPath);
|
||||
}
|
||||
|
||||
public void setCover(String albumPath, String imagePath) {
|
||||
@@ -54,21 +72,60 @@ public class AlbumCoverDbHelper extends SQLiteOpenHelper {
|
||||
|
||||
public String getCover(String albumPath) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_IMAGE_PATH},
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_CROP_PATH, COLUMN_IMAGE_PATH},
|
||||
COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath}, null, null, null);
|
||||
String coverPath = null;
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
coverPath = cursor.getString(0);
|
||||
if (coverPath == null) {
|
||||
coverPath = cursor.getString(1);
|
||||
}
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return coverPath;
|
||||
}
|
||||
|
||||
public String getCropPath(String albumPath) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_CROP_PATH},
|
||||
COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath}, null, null, null);
|
||||
String cropPath = null;
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
cropPath = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return cropPath;
|
||||
}
|
||||
|
||||
public String getOriginalImagePath(String albumPath) {
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor cursor = db.query(TABLE_NAME, new String[]{COLUMN_IMAGE_PATH},
|
||||
COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath}, null, null, null);
|
||||
String imagePath = null;
|
||||
if (cursor != null) {
|
||||
if (cursor.moveToFirst()) {
|
||||
imagePath = cursor.getString(0);
|
||||
}
|
||||
cursor.close();
|
||||
}
|
||||
return imagePath;
|
||||
}
|
||||
|
||||
public void clearCover(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.delete(TABLE_NAME, COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath});
|
||||
ContentValues values = new ContentValues();
|
||||
values.putNull(COLUMN_CROP_PATH);
|
||||
db.update(TABLE_NAME, values, COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath});
|
||||
LogUtils.d(TAG, "clearCover: " + albumPath);
|
||||
}
|
||||
|
||||
public void deleteCover(String albumPath) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.delete(TABLE_NAME, COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath});
|
||||
LogUtils.d(TAG, "deleteCover: " + albumPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.RectF;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CropActivity extends AppCompatActivity {
|
||||
public static final String TAG = "CropActivity";
|
||||
public static final String EXTRA_IMAGE_URI = "image_uri";
|
||||
public static final String EXTRA_IMAGE_PATH = "image_path";
|
||||
public static final String EXTRA_ALBUM_PATH = "album_path";
|
||||
public static final String EXTRA_CROP_WIDTH = "crop_width";
|
||||
public static final String EXTRA_CROP_HEIGHT = "crop_height";
|
||||
|
||||
private CropCanvasView cropCanvasView;
|
||||
private ZoomContainerView zoomContainer;
|
||||
private Bitmap originalBitmap;
|
||||
private String imagePath;
|
||||
private String albumPath;
|
||||
private int cropWidth = 240;
|
||||
private int cropHeight = 120;
|
||||
private float cropRatio = 2.0f;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_crop);
|
||||
|
||||
imagePath = getIntent().getStringExtra(EXTRA_IMAGE_PATH);
|
||||
albumPath = getIntent().getStringExtra(EXTRA_ALBUM_PATH);
|
||||
cropWidth = getIntent().getIntExtra(EXTRA_CROP_WIDTH, 240);
|
||||
cropHeight = getIntent().getIntExtra(EXTRA_CROP_HEIGHT, 120);
|
||||
|
||||
Preferences prefs = new Preferences(this);
|
||||
if (cropWidth > 0 && cropHeight > 0) {
|
||||
cropRatio = (float) cropWidth / cropHeight;
|
||||
} else {
|
||||
cropRatio = prefs.getCoverRatio();
|
||||
cropWidth = (int) (120 * cropRatio);
|
||||
cropHeight = 120;
|
||||
}
|
||||
|
||||
findViewById(R.id.btn_close).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
||||
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() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
saveCroppedCover();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
final View colorView = findViewById(R.id.color_view);
|
||||
final GradientDrawable colorDrawable = (GradientDrawable) colorView.getBackground();
|
||||
cropCanvasView.setOnBackgroundColorChangedListener(new CropCanvasView.OnBackgroundColorChangedListener() {
|
||||
@Override
|
||||
public void onBackgroundColorChanged(int color) {
|
||||
colorDrawable.setColor(color);
|
||||
}
|
||||
});
|
||||
|
||||
cropCanvasView.setOnColorPickedListener(new CropCanvasView.OnColorPickedListener() {
|
||||
@Override
|
||||
public void onColorPicked(int color) {
|
||||
int pickX = cropCanvasView.getLastPickImageX();
|
||||
int pickY = cropCanvasView.getLastPickImageY();
|
||||
colorDrawable.setColor(color);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
colorDrawable.setColor(cropCanvasView.getBackgroundColor());
|
||||
|
||||
loadImage();
|
||||
}
|
||||
|
||||
private void loadImage() {
|
||||
try {
|
||||
if (imagePath != null && new File(imagePath).exists()) {
|
||||
originalBitmap = BitmapFactory.decodeFile(imagePath);
|
||||
} else {
|
||||
Uri imageUri = getIntent().getParcelableExtra(EXTRA_IMAGE_URI);
|
||||
if (imageUri != null) {
|
||||
originalBitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
|
||||
}
|
||||
}
|
||||
|
||||
if (originalBitmap != null) {
|
||||
cropCanvasView.setImageBitmap(originalBitmap);
|
||||
cropCanvasView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initCrop();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "loadImage error: " + e.getMessage());
|
||||
Toast.makeText(this, "Failed to load image", Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void initCrop() {
|
||||
cropCanvasView.initCanvas(originalBitmap.getWidth(), originalBitmap.getHeight(), cropRatio);
|
||||
|
||||
int viewW = cropCanvasView.getWidth();
|
||||
int viewH = cropCanvasView.getHeight();
|
||||
if (viewW > 0 && viewH > 0) {
|
||||
cropCanvasView.scaleToView(viewW, viewH);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveCroppedCover() {
|
||||
if (originalBitmap == null || originalBitmap.isRecycled()) {
|
||||
Toast.makeText(this, "Failed to get image", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
int bmpX = (int) cropRect.left;
|
||||
int bmpY = (int) cropRect.top;
|
||||
int bmpW = (int) cropRect.width();
|
||||
int bmpH = (int) cropRect.height();
|
||||
|
||||
bmpX = Math.max(0, Math.min(bmpX, canvasBitmap.getWidth() - 1));
|
||||
bmpY = Math.max(0, Math.min(bmpY, canvasBitmap.getHeight() - 1));
|
||||
bmpW = Math.max(1, Math.min(bmpW, canvasBitmap.getWidth() - bmpX));
|
||||
bmpH = Math.max(1, Math.min(bmpH, canvasBitmap.getHeight() - bmpY));
|
||||
|
||||
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());
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(coverFile);
|
||||
cropped.compress(Bitmap.CompressFormat.PNG, 100, fos);
|
||||
fos.close();
|
||||
|
||||
LogUtils.d(TAG, "saveCroppedCover: file exists=" + coverFile.exists() + ", length=" + coverFile.length());
|
||||
|
||||
cropped.recycle();
|
||||
|
||||
AlbumCoverDbHelper coverDbHelper = AlbumCoverDbHelper.getInstance(this);
|
||||
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();
|
||||
setResult(RESULT_OK);
|
||||
finish();
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "saveCroppedCover error: " + e.getMessage());
|
||||
Toast.makeText(this, "Failed to save cover", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (originalBitmap != null) {
|
||||
originalBitmap.recycle();
|
||||
originalBitmap = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,689 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
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 {
|
||||
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 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;
|
||||
private float coverRatio = 2.0f;
|
||||
|
||||
private int extendHeight;
|
||||
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 Bitmap canvasBitmap;
|
||||
private float bitmapScale = 1.0f;
|
||||
private Bitmap displayBitmap;
|
||||
private RectF initialSpanRect;
|
||||
private float initialSpan;
|
||||
private int backgroundColor = Color.BLUE;
|
||||
private boolean colorPickMode = false;
|
||||
private int previewColor = 0;
|
||||
private float lastPickX = 0;
|
||||
private float lastPickY = 0;
|
||||
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) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public CropCanvasView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public CropCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (displayBitmap != null && displayBitmap != originalBitmap) {
|
||||
displayBitmap.recycle();
|
||||
}
|
||||
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) {
|
||||
this.imageWidth = imgWidth;
|
||||
this.imageHeight = imgHeight;
|
||||
this.coverRatio = ratio;
|
||||
|
||||
float imgRatio = (float) imgWidth / imgHeight;
|
||||
|
||||
if (coverRatio >= imgRatio) {
|
||||
extendHeight = imgHeight;
|
||||
extendWidth = (int) (imgHeight * coverRatio);
|
||||
} else {
|
||||
extendWidth = imgWidth;
|
||||
extendHeight = (int) (imgWidth / coverRatio);
|
||||
}
|
||||
|
||||
canvasHeight = Math.max(imageHeight, extendHeight);
|
||||
canvasWidth = Math.max(imageWidth, extendWidth);
|
||||
|
||||
float left = (canvasWidth - imageWidth) / 2f;
|
||||
float top = (canvasHeight - imageHeight) / 2f;
|
||||
imageBounds.set(left, top, left + imageWidth, top + imageHeight);
|
||||
canvasBounds.set(0, 0, canvasWidth, canvasHeight);
|
||||
|
||||
cropRect = new RectF(canvasBounds);
|
||||
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public void getDisplayMatrix(Matrix matrix) {
|
||||
if (canvasWidth <= 0 || canvasHeight <= 0 || getWidth() <= 0 || getHeight() <= 0) {
|
||||
return;
|
||||
}
|
||||
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 float screenToImageX(float screenX) {
|
||||
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) {
|
||||
this.backgroundColor = color;
|
||||
if (backgroundColorChangedListener != null) {
|
||||
backgroundColorChangedListener.onBackgroundColorChanged(color);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public int getBackgroundColor() {
|
||||
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) {
|
||||
this.colorPickMode = enable;
|
||||
if (enable) {
|
||||
pickX = -1;
|
||||
pickY = -1;
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean isColorPickMode() {
|
||||
return colorPickMode;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
float left = (canvasWidth - imageWidth) / 2f;
|
||||
float top = (canvasHeight - imageHeight) / 2f;
|
||||
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;
|
||||
}
|
||||
|
||||
public int getCanvasHeight() {
|
||||
return canvasHeight;
|
||||
}
|
||||
|
||||
public RectF getCanvasBounds() {
|
||||
return new RectF(canvasBounds);
|
||||
}
|
||||
|
||||
public RectF getImageBounds() {
|
||||
return new RectF(imageBounds);
|
||||
}
|
||||
|
||||
public float getBitmapScale() {
|
||||
return displayScale;
|
||||
}
|
||||
|
||||
public float getDisplayScale() {
|
||||
return displayScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int w = canvasWidth > 0 ? canvasWidth : 0;
|
||||
int h = canvasHeight > 0 ? canvasHeight : 0;
|
||||
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
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (cropRect == null) return;
|
||||
|
||||
if (displayBitmap != null && !displayBitmap.isRecycled()) {
|
||||
canvas.save();
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
getDisplayMatrix(matrix);
|
||||
canvas.concat(matrix);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
float newLeft = centerX - newWidth / 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;
|
||||
invalidate();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
float x = event.getX();
|
||||
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 (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
if (originalBitmap != null && !originalBitmap.isRecycled()) {
|
||||
float imgX = screenToImageX(x);
|
||||
float imgY = screenToImageY(y);
|
||||
if (imageBounds.contains(imgX, imgY)) {
|
||||
previewColor = getImageColorAt(x, y);
|
||||
} else if (canvasBounds.contains(x, y)) {
|
||||
previewColor = backgroundColor;
|
||||
} else {
|
||||
previewColor = Color.TRANSPARENT;
|
||||
}
|
||||
lastPickX = x;
|
||||
lastPickY = y;
|
||||
if (backgroundColorChangedListener != null) {
|
||||
backgroundColorChangedListener.onBackgroundColorChanged(previewColor);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
if (canvasBounds.contains(x, y)) {
|
||||
int pickedColor;
|
||||
float imgX = screenToImageX(x);
|
||||
float imgY = screenToImageY(y);
|
||||
if (imageBounds.contains(imgX, imgY)) {
|
||||
int colorAtPoint = getImageColorAt(x, y);
|
||||
pickedColor = colorAtPoint;
|
||||
backgroundColor = colorAtPoint;
|
||||
} else {
|
||||
pickedColor = backgroundColor;
|
||||
}
|
||||
if (colorPickedListener != null) {
|
||||
colorPickedListener.onColorPicked(pickedColor);
|
||||
}
|
||||
}
|
||||
if (colorPickEndListener != null) {
|
||||
colorPickEndListener.onColorPickEnd();
|
||||
}
|
||||
previewColor = 0;
|
||||
invalidate();
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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 >= bounds.left && newRight <= bounds.right) {
|
||||
cropRect.left = newLeft;
|
||||
cropRect.right = newRight;
|
||||
}
|
||||
if (newTop >= bounds.top && newBottom <= bounds.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) {
|
||||
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;
|
||||
}
|
||||
if (dx2 <= touchArea && dy <= touchArea) {
|
||||
return CORNER_TOP_RIGHT;
|
||||
}
|
||||
if (dx <= touchArea && dy2 <= touchArea) {
|
||||
return CORNER_BOTTOM_LEFT;
|
||||
}
|
||||
if (dx2 <= touchArea && dy2 <= touchArea) {
|
||||
return CORNER_BOTTOM_RIGHT;
|
||||
}
|
||||
if (x > cropRect.left && x < cropRect.right && y > cropRect.top && y < cropRect.bottom) {
|
||||
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));
|
||||
}
|
||||
|
||||
public int getColorAt(float x, float y) {
|
||||
return getImageColorAt(x, y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
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 CropOverlayView extends View {
|
||||
public interface OnCanvasSizeChangedListener {
|
||||
void onCanvasSizeChanged(RectF canvasBounds);
|
||||
}
|
||||
|
||||
private OnCanvasSizeChangedListener canvasSizeChangedListener;
|
||||
|
||||
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 float targetRatio = 2.0f;
|
||||
private RectF canvasBounds = new RectF();
|
||||
private float minSize = 50;
|
||||
|
||||
public CropOverlayView(Context context) {
|
||||
super(context);
|
||||
initPaints();
|
||||
}
|
||||
|
||||
public CropOverlayView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initPaints();
|
||||
}
|
||||
|
||||
public CropOverlayView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initPaints();
|
||||
}
|
||||
|
||||
private void initPaints() {
|
||||
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 setTargetRatio(float ratio) {
|
||||
this.targetRatio = ratio;
|
||||
}
|
||||
|
||||
public float getTargetRatio() {
|
||||
return targetRatio;
|
||||
}
|
||||
|
||||
public void setOnCanvasSizeChangedListener(OnCanvasSizeChangedListener listener) {
|
||||
this.canvasSizeChangedListener = listener;
|
||||
}
|
||||
|
||||
public void initCanvas(int canvasW, int canvasH) {
|
||||
canvasBounds.set(0, 0, canvasW, canvasH);
|
||||
|
||||
cropRect = new RectF(0, 0, canvasW, canvasH);
|
||||
|
||||
if (canvasSizeChangedListener != null) {
|
||||
canvasSizeChangedListener.onCanvasSizeChanged(new RectF(canvasBounds));
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public RectF getCropRect() {
|
||||
return new RectF(cropRect);
|
||||
}
|
||||
|
||||
public RectF getCanvasBounds() {
|
||||
return new RectF(canvasBounds);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
canvas.drawColor(Color.TRANSPARENT);
|
||||
|
||||
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 false;
|
||||
|
||||
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 / targetRatio;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -94,6 +95,14 @@ public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder>
|
||||
this.albumPath = albumPath;
|
||||
}
|
||||
|
||||
public int getCropWidth() {
|
||||
return prefs != null ? prefs.getCoverWidth() : 240;
|
||||
}
|
||||
|
||||
public int getCropHeight() {
|
||||
return prefs != null ? prefs.getCoverHeight() : 120;
|
||||
}
|
||||
|
||||
public void refreshBg() {
|
||||
if (prefs != null) {
|
||||
bgType = prefs.getBgType();
|
||||
@@ -109,18 +118,40 @@ public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder>
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void showContextMenu(View view, final int position) {
|
||||
private void showContextMenu(final View view, final int position) {
|
||||
final String imagePath = imagePaths.get(position);
|
||||
final Uri imageUri = imageUrls.get(position);
|
||||
final boolean[] isPinned = {pinnedDbHelper != null && pinnedDbHelper.isPinned(imagePath)};
|
||||
final boolean[] isCover = {false};
|
||||
if (coverDbHelper != null && albumPath != null) {
|
||||
String originalPath = coverDbHelper.getOriginalImagePath(albumPath);
|
||||
isCover[0] = originalPath != null && originalPath.equals(imagePath);
|
||||
}
|
||||
|
||||
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(view.getContext());
|
||||
builder.setTitle("Image");
|
||||
boolean isPinned = pinnedDbHelper != null && pinnedDbHelper.isPinned(imagePath);
|
||||
String[] items = isPinned ? new String[]{"取消置顶", "设置为封面"} : new String[]{"置顶", "设置为封面"};
|
||||
|
||||
String[] items;
|
||||
if (isPinned[0]) {
|
||||
if (isCover[0]) {
|
||||
items = new String[]{"取消置顶", "调整封面", "取消封面"};
|
||||
} else {
|
||||
items = new String[]{"取消置顶", "设置为封面"};
|
||||
}
|
||||
} else {
|
||||
if (isCover[0]) {
|
||||
items = new String[]{"置顶", "调整封面", "取消封面"};
|
||||
} else {
|
||||
items = new String[]{"置顶", "设置为封面"};
|
||||
}
|
||||
}
|
||||
|
||||
builder.setItems(items, new android.content.DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(android.content.DialogInterface dialog, int which) {
|
||||
if (which == 0) {
|
||||
if (pinnedDbHelper != null) {
|
||||
if (isPinned) {
|
||||
if (isPinned[0]) {
|
||||
pinnedDbHelper.unpinImage(imagePath);
|
||||
} else {
|
||||
pinnedDbHelper.pinImage(imagePath);
|
||||
@@ -129,7 +160,17 @@ public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder>
|
||||
}
|
||||
} else if (which == 1) {
|
||||
if (coverDbHelper != null && albumPath != null) {
|
||||
coverDbHelper.setCover(albumPath, imagePath);
|
||||
Intent cropIntent = new Intent(view.getContext(), CropActivity.class);
|
||||
cropIntent.putExtra(CropActivity.EXTRA_IMAGE_URI, imageUri);
|
||||
cropIntent.putExtra(CropActivity.EXTRA_IMAGE_PATH, imagePath);
|
||||
cropIntent.putExtra(CropActivity.EXTRA_ALBUM_PATH, albumPath);
|
||||
cropIntent.putExtra(CropActivity.EXTRA_CROP_WIDTH, getCropWidth());
|
||||
cropIntent.putExtra(CropActivity.EXTRA_CROP_HEIGHT, getCropHeight());
|
||||
((AlbumActivity) view.getContext()).startActivityForResult(cropIntent, 100);
|
||||
}
|
||||
} else if (which == 2) {
|
||||
if (coverDbHelper != null && albumPath != null && isCover[0]) {
|
||||
coverDbHelper.deleteCover(albumPath);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
@@ -162,7 +203,11 @@ public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder>
|
||||
boolean isPinned = pinnedDbHelper != null && pinnedDbHelper.isPinned(imagePath);
|
||||
holder.pinIcon.setVisibility(isPinned ? View.VISIBLE : View.GONE);
|
||||
|
||||
boolean isCover = coverDbHelper != null && albumPath != null && imagePath.equals(coverDbHelper.getCover(albumPath));
|
||||
boolean isCover = false;
|
||||
if (coverDbHelper != null && albumPath != null) {
|
||||
String originalPath = coverDbHelper.getOriginalImagePath(albumPath);
|
||||
isCover = originalPath != null && originalPath.equals(imagePath);
|
||||
}
|
||||
holder.coverIcon.setVisibility(isCover ? View.VISIBLE : View.GONE);
|
||||
|
||||
holder.itemView.setOnClickListener(new OnClickListener() {
|
||||
@@ -199,4 +244,4 @@ public class ImageAdapter extends RecyclerView.Adapter<ImageAdapter.ViewHolder>
|
||||
coverIcon = itemView.findViewById(R.id.cover_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.view.View;
|
||||
import android.view.View.OnTouchListener;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Toast;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
@@ -34,6 +35,7 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha
|
||||
private ImageButton btnShare;
|
||||
private ImageButton btnInfo;
|
||||
private ImageButton btnBg;
|
||||
private ImageButton btnGallery;
|
||||
private int bgType = 0;
|
||||
private GestureDetector gestureDetector;
|
||||
private TrashManager trashManager;
|
||||
@@ -63,6 +65,8 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha
|
||||
btnInfo = findViewById(R.id.btn_info);
|
||||
btnBg = findViewById(R.id.btn_bg);
|
||||
|
||||
btnGallery = findViewById(R.id.btn_gallery);
|
||||
|
||||
applyBg();
|
||||
|
||||
ImagePagerAdapter adapter = new ImagePagerAdapter(imageUrls, bgType);
|
||||
@@ -119,6 +123,20 @@ public class ImageViewerActivity extends Activity implements ViewPager.OnPageCha
|
||||
switchBg();
|
||||
}
|
||||
});
|
||||
|
||||
btnGallery.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Toast.makeText(ImageViewerActivity.this, "Gallery", Toast.LENGTH_SHORT).show();
|
||||
if (imageUrls != null && currentPosition >= 0 && currentPosition < imageUrls.size()) {
|
||||
Uri imageUri = imageUrls.get(currentPosition);
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setDataAndType(imageUri, "image/*");
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(Intent.createChooser(intent, "打开相册"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void toggleToolbar() {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cc.winboll.studio.gallery;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.media.MediaScannerConnection;
|
||||
@@ -192,9 +195,17 @@ private void loadAlbums() {
|
||||
LogUtils.d(TAG, "scanning folder: " + subfolder.getName());
|
||||
String albumPath = subfolder.getAbsolutePath();
|
||||
String coverPath = coverDbHelper.getCover(albumPath);
|
||||
LogUtils.d(TAG, "loadAlbums: album=" + albumPath + ", coverPath=" + coverPath);
|
||||
Uri coverUri = null;
|
||||
if (coverPath != null) {
|
||||
coverUri = getUriFromPath(coverPath);
|
||||
File coverFile = new File(coverPath);
|
||||
if (coverFile.exists()) {
|
||||
coverUri = Uri.fromFile(coverFile);
|
||||
LogUtils.d(TAG, "loadAlbums: cover from file=" + coverFile.getAbsolutePath());
|
||||
} else {
|
||||
coverUri = getUriFromPath(coverPath);
|
||||
LogUtils.d(TAG, "loadAlbums: cover from media store path=" + coverPath);
|
||||
}
|
||||
}
|
||||
if (coverUri == null) {
|
||||
ArrayList<Uri> images = getImagesInFolder(albumPath);
|
||||
@@ -278,29 +289,41 @@ private void loadAlbums() {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
if (id == R.id.action_settings) {
|
||||
if (id == R.id.action_mi_gallery) {
|
||||
Toast.makeText(this, "Gallery clicked", Toast.LENGTH_SHORT).show();
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_APP_GALLERY);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
} else if (id == R.id.action_settings) {
|
||||
startActivity(new Intent(this, SettingsActivity.class));
|
||||
return true;
|
||||
} else if (id == R.id.action_about) {
|
||||
startActivity(new Intent(this, AboutActivity.class));
|
||||
return true;
|
||||
} else if (id == R.id.action_trash) {
|
||||
startActivity(new Intent(this, TrashActivity.class));
|
||||
return true;
|
||||
} else if (id == R.id.action_refresh) {
|
||||
if (checkPermission()) {
|
||||
loadAlbums();
|
||||
}
|
||||
return true;
|
||||
} else if (id == R.id.action_debug) {
|
||||
LogActivity.startLogActivity(this);
|
||||
// Log.d("Gallery", "Debug log message");
|
||||
// Toast.makeText(this, R.string.debug_message, Toast.LENGTH_SHORT).show();
|
||||
LogActivity.startLogActivity(this);
|
||||
return true;
|
||||
}
|
||||
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
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
registerReceiver(coverUpdatedReceiver, new IntentFilter(Preferences.ACTION_COVER_UPDATED));
|
||||
if (checkPermission()) {
|
||||
scanMediaStore();
|
||||
loadAlbums();
|
||||
@@ -308,9 +331,16 @@ private void loadAlbums() {
|
||||
if (adapter != null) {
|
||||
adapter.refreshBg();
|
||||
adapter.refreshPinned();
|
||||
adapter.refreshCover();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
unregisterReceiver(coverUpdatedReceiver);
|
||||
}
|
||||
|
||||
private void scanMediaStore() {
|
||||
String folderPath = prefs.getFolderPath();
|
||||
File baseFolder = new File(folderPath);
|
||||
|
||||
@@ -10,8 +10,17 @@ public class Preferences {
|
||||
private static final String KEY_FOLDER_PATH = "folder_path";
|
||||
private static final String KEY_BG_TYPE = "bg_type";
|
||||
private static final String KEY_ALBUM_SORT_MODE = "album_sort_mode";
|
||||
private static final String KEY_COVER_WIDTH = "cover_width";
|
||||
private static final String KEY_COVER_HEIGHT = "cover_height";
|
||||
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_SORT_MODE = 0;
|
||||
private static final int DEFAULT_COVER_WIDTH = 240;
|
||||
private static final int DEFAULT_COVER_HEIGHT = 120;
|
||||
private static final float DEFAULT_COVER_RATIO = 2.0f;
|
||||
private static final String DEFAULT_PATH = "/storage/emulated/0/Pictures/Gallery/owner";
|
||||
|
||||
public static final int SORT_TIME_DESC = 0;
|
||||
@@ -57,4 +66,28 @@ public class Preferences {
|
||||
LogUtils.d(TAG, "setAlbumSortMode: " + mode);
|
||||
prefs.edit().putInt(KEY_ALBUM_SORT_MODE, mode).apply();
|
||||
}
|
||||
|
||||
public int getCoverWidth() {
|
||||
return prefs.getInt(KEY_COVER_WIDTH, DEFAULT_COVER_WIDTH);
|
||||
}
|
||||
|
||||
public void setCoverWidth(int width) {
|
||||
prefs.edit().putInt(KEY_COVER_WIDTH, width).apply();
|
||||
}
|
||||
|
||||
public int getCoverHeight() {
|
||||
return prefs.getInt(KEY_COVER_HEIGHT, DEFAULT_COVER_HEIGHT);
|
||||
}
|
||||
|
||||
public void setCoverHeight(int height) {
|
||||
prefs.edit().putInt(KEY_COVER_HEIGHT, height).apply();
|
||||
}
|
||||
|
||||
public float getCoverRatio() {
|
||||
return prefs.getFloat(KEY_COVER_RATIO, DEFAULT_COVER_RATIO);
|
||||
}
|
||||
|
||||
public void setCoverRatio(float ratio) {
|
||||
prefs.edit().putFloat(KEY_COVER_RATIO, ratio).apply();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package cc.winboll.studio.gallery.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import cc.winboll.studio.gallery.R;
|
||||
|
||||
/**
|
||||
* 颜色表对话框
|
||||
* 继承于普通对话框类,使用视图文件
|
||||
*/
|
||||
public class ColorPaletteDialog extends Dialog {
|
||||
|
||||
public ColorPaletteDialog(@NonNull Context context) {
|
||||
super(context, R.style.ColorPaletteDialog);
|
||||
}
|
||||
|
||||
public ColorPaletteDialog(@NonNull Context context, int themeResId) {
|
||||
super(context, themeResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.dialog_color_palette);
|
||||
|
||||
TextView titleText = findViewById(R.id.title_text);
|
||||
|
||||
WindowManager.LayoutParams params = getWindow().getAttributes();
|
||||
params.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||
getWindow().setAttributes(params);
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
|
||||
}
|
||||
|
||||
public interface OnColorItemClick {
|
||||
void onColorClick(int color);
|
||||
}
|
||||
}
|
||||
7
gallery/src/main/res/drawable/bg_color_circle.xml
Normal file
7
gallery/src/main/res/drawable/bg_color_circle.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?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/transparent"/>
|
||||
<stroke android:width="2dp" android:color="@android:color/white"/>
|
||||
<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>
|
||||
10
gallery/src/main/res/drawable/ic_close.xml
Normal file
10
gallery/src/main/res/drawable/ic_close.xml
Normal 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="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
10
gallery/src/main/res/drawable/ic_color_pick.xml
Normal file
10
gallery/src/main/res/drawable/ic_color_pick.xml
Normal 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>
|
||||
@@ -5,9 +5,9 @@
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M21,3H3C1.9,3 1,3.9 1,5v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2V5C23,3.9 22.1,3 21,3zM21,19H3V5h18V19z"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M9,12l2,2l4,-4l1.5,1.5L11,17l-3,-3z"/>
|
||||
</vector>
|
||||
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>
|
||||
16
gallery/src/main/res/drawable/ic_mi_gallery.xml
Normal file
16
gallery/src/main/res/drawable/ic_mi_gallery.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?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="#000000"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8s8,3.59 8,8s-3.59,8 -8,8z"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M12,7c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5s-2.24,-5 -5,-5zM12,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3s3,1.34 3,3s-1.34,3 -3,3z"/>
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M8,12l1.5,-2l2,2.5l2.5,-3l2,2.5"/>
|
||||
</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>
|
||||
33
gallery/src/main/res/layout/activity_about.xml
Normal file
33
gallery/src/main/res/layout/activity_about.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<cc.winboll.studio.libappbase.views.AboutView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/aboutview"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
143
gallery/src/main/res/layout/activity_crop.xml
Normal file
143
gallery/src/main/res/layout/activity_crop.xml
Normal file
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/colorPrimary"
|
||||
android:paddingHorizontal="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_close"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_close"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textview_window_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="封面剪裁"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="18sp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/color_icon_container"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:background="@android:color/transparent">
|
||||
|
||||
<View
|
||||
android:id="@+id/color_view"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/bg_color_circle"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_color_pick"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_color_pick"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_info"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_info"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_done"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_done"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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>
|
||||
@@ -36,6 +36,14 @@
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_gallery"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_cover"
|
||||
android:contentDescription="Gallery"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btn_share"
|
||||
android:layout_width="48dp"
|
||||
|
||||
16
gallery/src/main/res/layout/dialog_color_palette.xml
Normal file
16
gallery/src/main/res/layout/dialog_color_palette.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_dialog"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</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>
|
||||
8
gallery/src/main/res/menu/menu_crop.xml
Normal file
8
gallery/src/main/res/menu/menu_crop.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_done"
|
||||
android:title="完成"
|
||||
app:showAsAction="always"/>
|
||||
</menu>
|
||||
@@ -2,6 +2,12 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_mi_gallery"
|
||||
android:title="@string/mi_gallery"
|
||||
android:icon="@drawable/ic_mi_gallery"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_trash"
|
||||
android:title="@string/trash"
|
||||
@@ -13,8 +19,8 @@
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_refresh"
|
||||
android:title="@string/refresh"
|
||||
android:id="@+id/action_about"
|
||||
android:title="@string/about"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<resources>
|
||||
<string name="app_name">Gallery</string>
|
||||
<string name="app_description">WinBoLL Album App</string>
|
||||
<string name="refresh">Refresh</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="about">About</string>
|
||||
<string name="folder_path">Folder Path</string>
|
||||
<string name="enter_folder_path">Enter folder path</string>
|
||||
<string name="save">Save</string>
|
||||
@@ -16,4 +18,7 @@
|
||||
<string name="no">No</string>
|
||||
<string name="debug_log">Debug Log</string>
|
||||
<string name="debug_message">Debug log message</string>
|
||||
</resources>
|
||||
<string name="system_gallery">系统相册</string>
|
||||
<string name="mi_gallery">小米相册</string>
|
||||
<string name="reset_gallery">重置</string>
|
||||
</resources>
|
||||
|
||||
@@ -8,4 +8,9 @@
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="ColorPaletteDialog" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user