From 5b0bb599bbb5abf59207ad551e9dc9392cba7ebd Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sun, 26 Apr 2026 11:27:41 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E7=9B=B8=E5=86=8C=E9=9B=86?= =?UTF-8?q?=E5=B0=81=E9=9D=A2=E8=87=AA=E5=AE=9A=E4=B9=89=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 AlbumCoverDbHelper 数据库类,存储相册集封面路径 - 相册集浏览窗口长按菜单添加"设置为封面"选项 - 封面图片右下角显示封面图标标识 - 主窗口加载时优先使用设置的封面图片 - 修复 Cursor 在 try-with-resources 中的处理问题 --- gallery/build.properties | 4 +- .../winboll/studio/gallery/AlbumActivity.java | 1 + .../studio/gallery/AlbumCoverDbHelper.java | 74 +++++++++++++++++++ .../winboll/studio/gallery/ImageAdapter.java | 33 +++++++-- .../winboll/studio/gallery/MainActivity.java | 47 ++++++++++-- gallery/src/main/res/drawable/ic_cover.xml | 13 ++++ gallery/src/main/res/layout/item_gallery.xml | 13 ++++ 7 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 gallery/src/main/java/cc/winboll/studio/gallery/AlbumCoverDbHelper.java create mode 100644 gallery/src/main/res/drawable/ic_cover.xml diff --git a/gallery/build.properties b/gallery/build.properties index 5563358..cc68a40 100644 --- a/gallery/build.properties +++ b/gallery/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Apr 26 11:00:35 CST 2026 +#Sun Apr 26 11:19:12 CST 2026 stageCount=5 libraryProject= baseVersion=15.0 publishVersion=15.0.4 -buildCount=5 +buildCount=7 baseBetaVersion=15.0.5 diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/AlbumActivity.java b/gallery/src/main/java/cc/winboll/studio/gallery/AlbumActivity.java index 034d95d..90181b7 100644 --- a/gallery/src/main/java/cc/winboll/studio/gallery/AlbumActivity.java +++ b/gallery/src/main/java/cc/winboll/studio/gallery/AlbumActivity.java @@ -55,6 +55,7 @@ public class AlbumActivity extends AppCompatActivity { recyclerView.setLayoutManager(new GridLayoutManager(this, 3)); adapter = new ImageAdapter(); adapter.setContext(this); + adapter.setAlbumPath(albumPath); recyclerView.setAdapter(adapter); fabScrollTop = findViewById(R.id.fab_scroll_top); diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/AlbumCoverDbHelper.java b/gallery/src/main/java/cc/winboll/studio/gallery/AlbumCoverDbHelper.java new file mode 100644 index 0000000..a945bde --- /dev/null +++ b/gallery/src/main/java/cc/winboll/studio/gallery/AlbumCoverDbHelper.java @@ -0,0 +1,74 @@ +package cc.winboll.studio.gallery; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +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 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 SQL_CREATE = "CREATE TABLE " + TABLE_NAME + " (" + + COLUMN_ALBUM_PATH + " TEXT PRIMARY KEY, " + + COLUMN_IMAGE_PATH + " TEXT)"; + + private static AlbumCoverDbHelper dbHelper; + + public static AlbumCoverDbHelper getInstance(Context context) { + if (dbHelper == null) { + dbHelper = new AlbumCoverDbHelper(context.getApplicationContext()); + } + return dbHelper; + } + + public AlbumCoverDbHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(SQL_CREATE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); + onCreate(db); + } + + public void setCover(String albumPath, String imagePath) { + SQLiteDatabase db = getWritableDatabase(); + ContentValues values = new ContentValues(); + values.put(COLUMN_ALBUM_PATH, albumPath); + values.put(COLUMN_IMAGE_PATH, imagePath); + db.insertWithOnConflict(TABLE_NAME, null, values, SQLiteDatabase.CONFLICT_REPLACE); + LogUtils.d(TAG, "setCover: album=" + albumPath + ", image=" + imagePath); + } + + public String getCover(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 coverPath = null; + if (cursor != null) { + if (cursor.moveToFirst()) { + coverPath = cursor.getString(0); + } + cursor.close(); + } + return coverPath; + } + + public void clearCover(String albumPath) { + SQLiteDatabase db = getWritableDatabase(); + db.delete(TABLE_NAME, COLUMN_ALBUM_PATH + " = ?", new String[]{albumPath}); + LogUtils.d(TAG, "clearCover: " + albumPath); + } +} \ No newline at end of file diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/ImageAdapter.java b/gallery/src/main/java/cc/winboll/studio/gallery/ImageAdapter.java index 16a0b89..a4a621b 100644 --- a/gallery/src/main/java/cc/winboll/studio/gallery/ImageAdapter.java +++ b/gallery/src/main/java/cc/winboll/studio/gallery/ImageAdapter.java @@ -24,6 +24,8 @@ public class ImageAdapter extends RecyclerView.Adapter private int bgType = 0; private Preferences prefs; private PinnedImageDbHelper pinnedDbHelper; + private AlbumCoverDbHelper coverDbHelper; + private String albumPath; private int getBgRes() { switch (bgType) { @@ -85,6 +87,11 @@ public class ImageAdapter extends RecyclerView.Adapter prefs = new Preferences(context); bgType = prefs.getBgType(); pinnedDbHelper = PinnedImageDbHelper.getInstance(context); + coverDbHelper = AlbumCoverDbHelper.getInstance(context); + } + + public void setAlbumPath(String albumPath) { + this.albumPath = albumPath; } public void refreshBg() { @@ -107,17 +114,24 @@ public class ImageAdapter extends RecyclerView.Adapter 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 = isPinned ? new String[]{"取消置顶", "设置为封面"} : new String[]{"置顶", "设置为封面"}; builder.setItems(items, new android.content.DialogInterface.OnClickListener() { @Override public void onClick(android.content.DialogInterface dialog, int which) { - if (pinnedDbHelper != null && which == 0) { - if (isPinned) { - pinnedDbHelper.unpinImage(imagePath); - } else { - pinnedDbHelper.pinImage(imagePath); + if (which == 0) { + if (pinnedDbHelper != null) { + if (isPinned) { + pinnedDbHelper.unpinImage(imagePath); + } else { + pinnedDbHelper.pinImage(imagePath); + } + refreshPinned(); + } + } else if (which == 1) { + if (coverDbHelper != null && albumPath != null) { + coverDbHelper.setCover(albumPath, imagePath); + notifyDataSetChanged(); } - refreshPinned(); } } }); @@ -148,6 +162,9 @@ public class ImageAdapter extends RecyclerView.Adapter 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)); + holder.coverIcon.setVisibility(isCover ? View.VISIBLE : View.GONE); + holder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -174,10 +191,12 @@ public class ImageAdapter extends RecyclerView.Adapter static class ViewHolder extends RecyclerView.ViewHolder { ImageView imageView; ImageView pinIcon; + ImageView coverIcon; ViewHolder(View itemView) { super(itemView); imageView = itemView.findViewById(R.id.image); pinIcon = itemView.findViewById(R.id.pin_icon); + coverIcon = itemView.findViewById(R.id.cover_icon); } } } \ No newline at end of file diff --git a/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java b/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java index 607694b..ffe1462 100644 --- a/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java +++ b/gallery/src/main/java/cc/winboll/studio/gallery/MainActivity.java @@ -159,7 +159,7 @@ public class MainActivity extends AppCompatActivity { } } - private void loadAlbums() { +private void loadAlbums() { LogUtils.d(TAG, "loadAlbums"); String folderPath = prefs.getFolderPath(); File baseFolder = new File(folderPath); @@ -176,6 +176,7 @@ public class MainActivity extends AppCompatActivity { } } + AlbumCoverDbHelper coverDbHelper = AlbumCoverDbHelper.getInstance(this); ArrayList albums = new ArrayList<>(); FileFilter directoryFilter = new FileFilter() { @@ -189,11 +190,26 @@ public class MainActivity extends AppCompatActivity { if (subfolders != null) { for (File subfolder : subfolders) { LogUtils.d(TAG, "scanning folder: " + subfolder.getName()); - ArrayList images = getImagesInFolder(subfolder.getAbsolutePath()); - if (!images.isEmpty()) { - Uri latestImage = images.get(0); - albums.add(new Album(subfolder.getName(), subfolder.getAbsolutePath(), latestImage, images.size())); - LogUtils.d(TAG, "album added: " + subfolder.getName() + ", " + images.size() + " images"); + String albumPath = subfolder.getAbsolutePath(); + String coverPath = coverDbHelper.getCover(albumPath); + Uri coverUri = null; + if (coverPath != null) { + coverUri = getUriFromPath(coverPath); + } + if (coverUri == null) { + ArrayList images = getImagesInFolder(albumPath); + if (!images.isEmpty()) { + coverUri = images.get(0); + } + } + ArrayList allImages = getImagesInFolder(albumPath); + if (coverUri != null || !allImages.isEmpty()) { + if (coverUri == null && !allImages.isEmpty()) { + coverUri = allImages.get(0); + } + int imageCount = allImages.size(); + albums.add(new Album(subfolder.getName(), albumPath, coverUri, imageCount)); + LogUtils.d(TAG, "album added: " + subfolder.getName() + ", " + imageCount + " images"); } } } @@ -205,7 +221,24 @@ public class MainActivity extends AppCompatActivity { adapter.setData(albums); LogUtils.d(TAG, "Loaded " + albums.size() + " albums"); } - + + private Uri getUriFromPath(String path) { + String[] projection = { MediaStore.Images.Media._ID }; + String selection = MediaStore.Images.Media.DATA + " = ?"; + String[] selectionArgs = { path }; + try (Cursor cursor = getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, selection, selectionArgs, null)) { + if (cursor != null) { + if (cursor.moveToFirst()) { + long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)); + return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id)); + } + } + } + return null; + } + private ArrayList getImagesInFolder(String folderPath) { ArrayList imageUrls = new ArrayList<>(); ContentResolver contentResolver = getContentResolver(); diff --git a/gallery/src/main/res/drawable/ic_cover.xml b/gallery/src/main/res/drawable/ic_cover.xml new file mode 100644 index 0000000..6a34a05 --- /dev/null +++ b/gallery/src/main/res/drawable/ic_cover.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/gallery/src/main/res/layout/item_gallery.xml b/gallery/src/main/res/layout/item_gallery.xml index 3a76c93..2449ff1 100644 --- a/gallery/src/main/res/layout/item_gallery.xml +++ b/gallery/src/main/res/layout/item_gallery.xml @@ -25,4 +25,17 @@ app:tint="@color/black" xmlns:app="http://schemas.android.com/apk/res-auto"/> + + \ No newline at end of file