From 51793077bd2ca909a33f16c1518e3027c6d4fd6e Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Fri, 21 Nov 2025 18:24:22 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9B=BE=E7=89=87=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E9=A2=84=E8=A7=88=E5=8A=9F=E8=83=BD=E4=B8=8E=E4=B8=80?= =?UTF-8?q?=E4=BA=9B=E8=B0=83=E8=AF=95=E5=85=A5=E5=8F=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../dialogs/NetworkBackgroundDialog.java | 19 +- .../unittest/BackgroundViewTestFragment.java | 11 + .../powerbell/views/BackgroundView.java | 394 ++++++++++++------ .../main/res/layout/activity_mainunittest.xml | 5 +- .../layout/fragment_test_backgroundview.xml | 15 +- 6 files changed, 300 insertions(+), 148 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index 6d56002b..cddc8c02 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Fri Nov 21 14:24:01 HKT 2025 +#Fri Nov 21 10:22:55 GMT 2025 stageCount=7 libraryProject= baseVersion=15.11 publishVersion=15.11.6 -buildCount=0 +buildCount=9 baseBetaVersion=15.11.7 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java index d6b7ad56..80b70cf1 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/dialogs/NetworkBackgroundDialog.java @@ -45,6 +45,7 @@ public class NetworkBackgroundDialog extends AlertDialog { Context mContext; // 主线程 Handler,用于接收子线程消息并更新 UI private Handler mUiHandler; + String previewFilePath; // 按钮点击回调接口(Java7 接口实现) public interface OnDialogClickListener { @@ -160,6 +161,9 @@ public class NetworkBackgroundDialog extends AlertDialog { @Override public void onClick(View v) { LogUtils.d("NetworkBackgroundDialog", "确认按钮点击"); + // 确定预览背景资源 + bvBackgroundPreview.saveToBackgroundSources(previewFilePath); + dismiss(); // 关闭对话框 if (listener != null) { listener.onConfirm(); @@ -192,18 +196,15 @@ public class NetworkBackgroundDialog extends AlertDialog { File imageFile = new File(filePath); if (!imageFile.exists()) { LogUtils.e(TAG, "图片文件不存在:" + filePath); - bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher); + ToastUtils.show("Test"); + //bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher); return; } - fis = new FileInputStream(imageFile); - Drawable drawable = Drawable.createFromStream(fis, null); - // 设置背景(主线程安全操作) - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { - bvBackgroundPreview.setBackground(drawable); - } else { - bvBackgroundPreview.setBackgroundDrawable(drawable); - } + // 预览背景 + previewFilePath = filePath; + bvBackgroundPreview.previewBackgroundImage(previewFilePath); + LogUtils.d(TAG, "图片预览成功:" + filePath); } catch (Exception e) { diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java index ac038ca8..10f820f1 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java @@ -8,6 +8,9 @@ import androidx.fragment.app.Fragment; import cc.winboll.studio.libappbase.GlobalApplication; import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.powerbell.R; +import android.widget.Button; +import cc.winboll.studio.powerbell.MainActivity; +import android.content.Intent; /** * @Author ZhanGSKen&豆包大模型 @@ -30,6 +33,14 @@ public class BackgroundViewTestFragment extends Fragment { } mainView = inflater.inflate(R.layout.fragment_test_backgroundview, container, false); + + ((Button)mainView.findViewById(R.id.btn_main_activity)).setOnClickListener(new View.OnClickListener(){ + + @Override + public void onClick(View v) { + getActivity().startActivity(new Intent(getActivity(), MainActivity.class)); + } + }); ToastUtils.show(String.format("%s onCreate", TAG)); return mainView; diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java index 924227ce..5a63951c 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java @@ -6,7 +6,7 @@ import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; -import android.view.View; +import android.widget.ImageView; import android.widget.RelativeLayout; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.powerbell.R; @@ -17,20 +17,23 @@ import java.io.FileOutputStream; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/11/19 18:01 - * @Describe 背景图片视图控件(加载外置存储 Background/current.jpg) + * @Describe 背景图片视图控件(支持预览临时图片 + 外部刷新) */ public class BackgroundView extends RelativeLayout { - + public static final String TAG = "BackgroundView"; - + Context mContext; - View mMianView; - - // 外置存储背景图片路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg + private ImageView ivBackground; + private static String BACKGROUND_IMAGE_FOLDER = "Background"; private static String BACKGROUND_IMAGE_FILENAME = "current.data"; - private static String backgroundSourceFilePath; - + private static String BACKGROUND_IMAGE_PREVIEW_FILENAME = "current_preview.data"; + private static String backgroundSourceFilePath; + private float imageAspectRatio = 1.0f; // 默认 1:1 + // 标记当前是否处于预览状态 + private boolean isPreviewMode = false; + public BackgroundView(Context context) { super(context); this.mContext = context; @@ -56,172 +59,288 @@ public class BackgroundView extends RelativeLayout { } void initView() { - // 1. 初始化外置存储背景图片路径(应用私有外置存储,无需动态权限) + initBackgroundImageView(); initBackgroundImagePath(); - - // 2. 加载并设置背景图片 - loadAndSetBackground(); + loadAndSetImageViewBackground(); + } + + private void initBackgroundImageView() { + ivBackground = new ImageView(mContext); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); + ivBackground.setLayoutParams(layoutParams); + ivBackground.setScaleType(ImageView.ScaleType.FIT_CENTER); + this.addView(ivBackground); } - /** - * 初始化外置存储背景图片路径 - * 路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg - */ private void initBackgroundImagePath() { - // 获取应用私有外置存储的 files 目录(API 19+ 无需权限) File externalFilesDir = mContext.getExternalFilesDir(null); if (externalFilesDir == null) { LogUtils.e(TAG, "外置存储不可用,无法初始化背景图片路径"); return; } - - // 拼接 Background 目录和 current.jpg 文件路径 - File backgroundDir = new File(externalFilesDir, "Background"); - if(!backgroundDir.exists()){ - backgroundDir.mkdirs(); - } - //BACKGROUND_IMAGE_PATH = new File(backgroundDir, "current.jpg").getAbsolutePath(); - //BACKGROUND_IMAGE_PATH = new File(backgroundDir, "current.data").getAbsolutePath(); - backgroundSourceFilePath = new File(backgroundDir, BACKGROUND_IMAGE_FILENAME).getAbsolutePath(); - //LogUtils.d(TAG, "背景图片路径:" + BACKGROUND_IMAGE_PATH); + + File backgroundDir = new File(externalFilesDir, BACKGROUND_IMAGE_FOLDER); + if (!backgroundDir.exists()) { + backgroundDir.mkdirs(); + } + backgroundSourceFilePath = new File(backgroundDir, BACKGROUND_IMAGE_FILENAME).getAbsolutePath(); } - - /** - * 拷贝图片文件到背景资源目录 - * @param srcBackgroundPath 源文件路径(待拷贝的图片路径) - */ - public void saveToBackgroundSources(String srcBackgroundPath) { - // 1. 初始化目标路径(确保路径有效) - initBackgroundImagePath(); - if (backgroundSourceFilePath == null) { - LogUtils.e(TAG, "目标路径初始化失败,无法保存背景图片"); - return; - } - - // 2. 校验源文件 - File srcFile = new File(srcBackgroundPath); - if (!srcFile.exists() || !srcFile.isFile()) { - LogUtils.e(TAG, String.format("源文件不存在或不是文件:%s", srcBackgroundPath)); - return; - } - - // 3. 创建目标目录(若不存在) - File destFile = new File(backgroundSourceFilePath); - File destDir = destFile.getParentFile(); - if (destDir != null && !destDir.exists()) { - boolean isDirCreated = destDir.mkdirs(); - if (!isDirCreated) { - LogUtils.e(TAG, "目标目录创建失败:" + destDir.getAbsolutePath()); - return; - } - } - - // 4. 文件流拷贝(Java 7 原生实现,兼容低版本) - FileInputStream fis = null; - FileOutputStream fos = null; - try { - fis = new FileInputStream(srcFile); - fos = new FileOutputStream(destFile); // 覆盖已有文件(如需询问用户可在此处添加逻辑) - - // 缓冲区拷贝(提升效率,避免频繁 IO 操作) - byte[] buffer = new byte[4096]; - int len; - while ((len = fis.read(buffer)) != -1) { - fos.write(buffer, 0, len); - } - fos.flush(); // 强制刷新缓冲区,确保数据完整写入 - - LogUtils.d(TAG, String.format("文件拷贝成功:%s -> %s", srcBackgroundPath, backgroundSourceFilePath)); - - } catch (Exception e) { - // 捕获所有异常,避免崩溃 - LogUtils.e(TAG, String.format("文件拷贝失败:%s", e.getMessage()), e); - // 拷贝失败时删除目标文件(避免生成损坏文件) - if (destFile.exists()) { - destFile.delete(); - LogUtils.d(TAG, "已删除损坏的目标文件"); - } - } finally { - // 5. 关闭流(Java 7 手动关闭,避免资源泄漏) - if (fis != null) { - try { - fis.close(); - } catch (Exception e) { - LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage()); - } - } - if (fos != null) { - try { - fos.close(); - } catch (Exception e) { - LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage()); - } - } - } - } /** - * 加载外置存储的 current.jpg 并设置为控件背景 - * 支持:文件压缩、版本兼容、异常兜底 + * 拷贝图片文件到背景资源目录(正式背景) */ - private void loadAndSetBackground() { - // 校验路径是否有效 + public void saveToBackgroundSources(String srcBackgroundPath) { + initBackgroundImagePath(); if (backgroundSourceFilePath == null) { - setDefaultBackground(); + LogUtils.e(TAG, "目标路径初始化失败,无法保存背景图片"); + return; + } + + File srcFile = new File(srcBackgroundPath); + if (!srcFile.exists() || !srcFile.isFile()) { + LogUtils.e(TAG, String.format("源文件不存在或不是文件:%s", srcBackgroundPath)); + return; + } + + File destFile = new File(backgroundSourceFilePath); + File destDir = destFile.getParentFile(); + if (destDir != null && !destDir.exists()) { + boolean isDirCreated = destDir.mkdirs(); + if (!isDirCreated) { + LogUtils.e(TAG, "目标目录创建失败:" + destDir.getAbsolutePath()); + return; + } + } + + FileInputStream fis = null; + FileOutputStream fos = null; + try { + fis = new FileInputStream(srcFile); + fos = new FileOutputStream(destFile); + + byte[] buffer = new byte[4096]; + int len; + while ((len = fis.read(buffer)) != -1) { + fos.write(buffer, 0, len); + } + fos.flush(); + + LogUtils.d(TAG, String.format("文件拷贝成功:%s -> %s", srcBackgroundPath, backgroundSourceFilePath)); + // 拷贝成功后,若处于预览模式则退出预览,加载正式背景 + if (isPreviewMode) { + exitPreviewMode(); + } else { + loadAndSetImageViewBackground(); + } + + } catch (Exception e) { + LogUtils.e(TAG, String.format("文件拷贝失败:%s", e.getMessage()), e); + if (destFile.exists()) { + destFile.delete(); + LogUtils.d(TAG, "已删除损坏的目标文件"); + } + } finally { + if (fis != null) { + try { + fis.close(); + } catch (Exception e) { + LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage()); + } + } + if (fos != null) { + try { + fos.close(); + } catch (Exception e) { + LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage()); + } + } + } + } + + /** + * 【新增公共函数】预览临时图片(不修改正式背景文件) + * @param previewImagePath 临时预览图片的路径 + */ + public void previewBackgroundImage(String previewImagePath) { + if (previewImagePath == null || previewImagePath.isEmpty()) { + LogUtils.e(TAG, "预览图片路径为空"); + return; + } + + File previewFile = new File(previewImagePath); + if (!previewFile.exists() || !previewFile.isFile()) { + LogUtils.e(TAG, "预览图片不存在或不是文件:" + previewImagePath); + return; + } + + // 计算预览图片宽高比 + if (!calculateImageAspectRatio(previewFile)) { + LogUtils.e(TAG, "预览图片尺寸无效,无法预览"); + return; + } + + // 压缩加载预览图片 + Bitmap previewBitmap = decodeBitmapWithCompress(previewFile, 1080, 1920); + if (previewBitmap == null) { + LogUtils.e(TAG, "预览图片加载失败"); + return; + } + + // 设置预览图片到 ImageView + Drawable previewDrawable = new BitmapDrawable(mContext.getResources(), previewBitmap); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { + ivBackground.setBackground(previewDrawable); + } else { + ivBackground.setBackgroundDrawable(previewDrawable); + } + + // 调整 ImageView 尺寸以匹配预览图片宽高比 + adjustImageViewSize(); + isPreviewMode = true; + LogUtils.d(TAG, "进入预览模式,预览图片路径:" + previewImagePath); + } + + /** + * 【新增公共函数】退出预览模式,恢复显示正式背景图片 + */ + public void exitPreviewMode() { + if (isPreviewMode) { + loadAndSetImageViewBackground(); + isPreviewMode = false; + LogUtils.d(TAG, "退出预览模式,恢复正式背景"); + } + } + + /** + * 公共函数:供外部类调用,重新加载正式背景图片(刷新显示) + */ + public void reloadBackgroundImage() { + LogUtils.d(TAG, "外部调用重新加载背景图片"); + initBackgroundImagePath(); + loadAndSetImageViewBackground(); + // 若处于预览模式,退出预览 + if (isPreviewMode) { + isPreviewMode = false; + } + } + + /** + * 加载正式背景图片并设置到 ImageView + */ + private void loadAndSetImageViewBackground() { + if (backgroundSourceFilePath == null) { + setDefaultImageViewBackground(); return; } File backgroundFile = new File(backgroundSourceFilePath); - // 校验文件是否存在 if (!backgroundFile.exists() || !backgroundFile.isFile()) { LogUtils.e(TAG, "背景图片不存在:" + backgroundSourceFilePath); - setDefaultBackground(); + setDefaultImageViewBackground(); return; } - // 3. 压缩加载 Bitmap(避免 OOM) - Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); // 最大宽高限制 + if (!calculateImageAspectRatio(backgroundFile)) { + setDefaultImageViewBackground(); + return; + } + + Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); if (bitmap == null) { LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap"); - setDefaultBackground(); + setDefaultImageViewBackground(); return; } - // 4. 设置为控件背景(兼容 Android 低版本) Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { - this.setBackground(backgroundDrawable); + ivBackground.setBackground(backgroundDrawable); } else { - this.setBackgroundDrawable(backgroundDrawable); + ivBackground.setBackgroundDrawable(backgroundDrawable); } - LogUtils.d(TAG, "背景图片加载成功"); + + adjustImageViewSize(); + LogUtils.d(TAG, "ImageView 背景加载成功,宽高比:" + imageAspectRatio); } /** - * 带压缩的 Bitmap 解码(避免大图导致 OOM) - * @param file 图片文件 - * @param maxWidth 最大宽度限制 - * @param maxHeight 最大高度限制 - * @return 压缩后的 Bitmap(null 表示失败) + * 计算图片宽高比(宽/高) + */ + private boolean calculateImageAspectRatio(File file) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + + int imageWidth = options.outWidth; + int imageHeight = options.outHeight; + + if (imageWidth <= 0 || imageHeight <= 0) { + LogUtils.e(TAG, "图片尺寸无效:宽=" + imageWidth + ", 高=" + imageHeight); + return false; + } + + imageAspectRatio = (float) imageWidth / imageHeight; + return true; + } catch (Exception e) { + LogUtils.e(TAG, "计算图片宽高比失败:" + e.getMessage()); + return false; + } + } + + /** + * 动态调整 ImageView 尺寸以匹配图片宽高比 + */ + private void adjustImageViewSize() { + int parentWidth = getWidth(); + int parentHeight = getHeight(); + + if (parentWidth == 0 || parentHeight == 0) { + post(new Runnable() { + @Override + public void run() { + adjustImageViewSize(); + } + }); + return; + } + + int imageViewWidth, imageViewHeight; + if (imageAspectRatio >= 1.0f) { // 横图 + imageViewWidth = Math.min(parentWidth, (int) (parentHeight * imageAspectRatio)); + imageViewHeight = (int) (imageViewWidth / imageAspectRatio); + } else { // 竖图 + imageViewHeight = Math.min(parentHeight, (int) (parentWidth / imageAspectRatio)); + imageViewWidth = (int) (imageViewHeight * imageAspectRatio); + } + + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivBackground.getLayoutParams(); + layoutParams.width = imageViewWidth; + layoutParams.height = imageViewHeight; + ivBackground.setLayoutParams(layoutParams); + } + + /** + * 带压缩的 Bitmap 解码(避免 OOM) */ private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) { try { BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; // 先获取图片尺寸,不加载到内存 + options.inJustDecodeBounds = true; BitmapFactory.decodeFile(file.getAbsolutePath(), options); - // 计算缩放比例 int scaleX = options.outWidth / maxWidth; int scaleY = options.outHeight / maxHeight; int inSampleSize = Math.max(scaleX, scaleY); if (inSampleSize <= 0) { - inSampleSize = 1; // 最小缩放比例为 1 + inSampleSize = 1; } - // 正式加载图片并压缩 options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; - options.inPreferredConfig = Bitmap.Config.RGB_565; // 降低像素格式,减少内存占用 + options.inPreferredConfig = Bitmap.Config.RGB_565; return BitmapFactory.decodeFile(file.getAbsolutePath(), options); } catch (Exception e) { LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage()); @@ -232,9 +351,24 @@ public class BackgroundView extends RelativeLayout { /** * 设置默认背景(图片加载失败时兜底) */ - private void setDefaultBackground() { - // 可替换为项目自定义的默认背景图 - this.setBackgroundResource(R.drawable.default_background); - LogUtils.d(TAG, "已设置默认背景"); + private void setDefaultImageViewBackground() { + ivBackground.setBackgroundResource(R.drawable.default_background); + imageAspectRatio = 1.0f; + adjustImageViewSize(); + LogUtils.d(TAG, "已设置 ImageView 默认背景"); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + adjustImageViewSize(); + } + + /** + * 对外提供:判断当前是否处于预览模式 + */ + public boolean isPreviewMode() { + return isPreviewMode; } } + diff --git a/powerbell/src/main/res/layout/activity_mainunittest.xml b/powerbell/src/main/res/layout/activity_mainunittest.xml index 661c79a7..b8e7d012 100644 --- a/powerbell/src/main/res/layout/activity_mainunittest.xml +++ b/powerbell/src/main/res/layout/activity_mainunittest.xml @@ -8,9 +8,8 @@ + android:layout_height="match_parent" + android:id="@+id/activitymainunittestFrameLayout1"/> diff --git a/powerbell/src/main/res/layout/fragment_test_backgroundview.xml b/powerbell/src/main/res/layout/fragment_test_backgroundview.xml index bef9a6c9..ee0828d5 100644 --- a/powerbell/src/main/res/layout/fragment_test_backgroundview.xml +++ b/powerbell/src/main/res/layout/fragment_test_backgroundview.xml @@ -7,10 +7,17 @@ android:layout_height="match_parent" android:background="#FF7381FF"> - + + +