diff --git a/powerbell/build.properties b/powerbell/build.properties index 003cea01..8c70ac16 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Sun Nov 30 18:48:20 HKT 2025 +#Sun Nov 30 16:39:04 GMT 2025 stageCount=13 libraryProject= baseVersion=15.11 publishVersion=15.11.12 -buildCount=0 +buildCount=4 baseBetaVersion=15.11.13 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java index 3cdb7d27..a6823a88 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/activities/BackgroundSettingsActivity.java @@ -52,46 +52,42 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private File mfBackgroundDir; // 背景图片存储文件夹 private File mfPictureDir; // 拍照与剪裁临时文件夹 private File mfTakePhoto; // 拍照文件 - //private File mfRecivedPicture; // 接收的图片文件 - // 背景视图预览图片的文件名 - private String preViewFilePath = ""; - private String preViewFileUrl = ""; - BackgroundView bvPreviewBackground; - boolean isCommitSettings = false; + // 背景视图预览图片的文件名 + private String preViewFilePath = ""; + private String preViewFileUrl = ""; + BackgroundView bvPreviewBackground; + boolean isCommitSettings = false; // 静态变量 - // 源文件的临时剪裁图片保存名称 + // 源文件的临时剪裁图片保存名称 private static String _mSourceCropTempFileName = "SourceCropTemp.jpg"; - // 源文件的临时剪裁图片保存文件对象 - private static File _mSourceCropTempFile; + // 源文件的临时剪裁图片保存文件对象 + private static File _mSourceCropTempFile; // 源文件的剪裁图片保存名称 private static String _mSourceCroppedFileName = "SourceCropped.jpg"; - // 源文件的剪裁图片保存文件对象 - private static File _mSourceCroppedFile; - // 源文件的剪裁图片保存路径 - private static String _mSourceCroppedFilePath; + // 源文件的剪裁图片保存文件对象 + private static File _mSourceCroppedFile; + // 源文件的剪裁图片保存路径 + private static String _mSourceCroppedFilePath; private static String _mszCommonFileType = "jpeg"; private int mnPictureCompress = 100; - //private static String _RecivedBackgroundFileName; - @Override - public Activity getActivity() { - return this; - } + @Override + public Activity getActivity() { + return this; + } - @Override - public String getTag() { - return TAG; - } + @Override + public String getTag() { + return TAG; + } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_backgroundpicture); - bvPreviewBackground = (BackgroundView) findViewById(R.id.activitybackgroundpictureBackgroundView1); - - //initEnv(); + bvPreviewBackground = (BackgroundView) findViewById(R.id.activitybackgroundpictureBackgroundView1); // 初始化工具类和文件夹 mBackgroundSourceUtils = BackgroundSourceUtils.getInstance(this); @@ -107,13 +103,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg // 初始化文件对象 mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg"); - //mfTempCropPicture = new File(mfPictureDir, "TempCrop.jpg"); - - //mfRecivedPicture = getRecivedPictureFile(); _mSourceCropTempFile = new File(mfBackgroundDir, _mSourceCropTempFileName); _mSourceCroppedFile = new File(mfBackgroundDir, _mSourceCroppedFileName); - _mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString(); - LogUtils.d(TAG, String.format("_mSourceCroppedFilePath : %s", _mSourceCroppedFilePath)); + _mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString(); + LogUtils.d(TAG, String.format("_mSourceCroppedFilePath : %s", _mSourceCroppedFilePath)); // 初始化工具栏 mAToolbar = (AToolbar) findViewById(R.id.toolbar); @@ -121,11 +114,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture); getActionBar().setDisplayHomeAsUpEnabled(true); mAToolbar.setNavigationOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); // 点击导航栏返回按钮,触发 finish() - } - }); + @Override + public void onClick(View v) { + finish(); // 点击导航栏返回按钮,触发 finish() + } + }); // 设置按钮点击事件 findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener); @@ -134,12 +127,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg findViewById(R.id.activitybackgroundpictureAButton2).setOnClickListener(onSelectPictureClickListener); findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener); findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener); - findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener); - findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener); + findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener); + findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener); + // 初始预览:加载当前背景到预览并刷新视图 BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); - utils.setCurrentSourceToPreview(); - bvPreviewBackground.reloadPreviewBackground(); + utils.setCurrentSourceToPreview(); + bvPreviewBackground.reloadPreviewBackground(); // 修复:调用预览刷新方法 // 处理分享的图片 Intent intent = getIntent(); @@ -152,74 +146,28 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } -// private void initEnv() { -// LogUtils.d(TAG, "initEnv()"); -// _RecivedBackgroundFileName = "SourcePicture.data"; -// } - public static String getBackgroundFileName() { return _mSourceCroppedFileName; } @Override public void onAcceptRecivedPicture(String szPreRecivedPictureName) { - ToastUtils.show("onAcceptRecivedPicture not yet."); -// BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); -// utils.getCurrentBackgroundBean().setIsUseBackgroundFile(true); -// utils.saveSettings(); -// -// File sourceFile = new File(utils.getBackgroundSourceDirPath(), szPreRecivedPictureName); -// if (FileUtils.copyFile(sourceFile, mfRecivedPicture)) { -// startCropImageActivity(false); -// } else { -// ToastUtils.show("图片复制失败,请重试"); -// } + ToastUtils.show("onAcceptRecivedPicture not yet."); } /** - * 更新背景图片预览, - * 如果sourceFile参数为空,则加载旧的背景图片资源 + * 更新背景图片预览 + * 如果sourceFile参数为空,则加载旧的背景图片资源 */ public void updateBackgroundView(File sourceFile, String sourceFileInfo) { LogUtils.d(TAG, "updatePreviewBackground"); - BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); - if (sourceFile == null) { - bvPreviewBackground.reloadCurrentBackground(); - } else { - utils.saveFileToPreviewBean(sourceFile, sourceFileInfo); - bvPreviewBackground.reloadPreviewBackground(); - } - -// boolean isUseBackgroundFile = utils.getCurrentBackgroundBean().isUseBackgroundFile(); -// LogUtils.d(TAG, String.format("isUseBackgroundFile is %s, _mSourceCroppedFile.exists() is %s ", isUseBackgroundFile, _mSourceCroppedFile.exists())); -// -// //if (isUseBackgroundFile && _mSourceCroppedFile.exists()) { -// if (_mSourceCroppedFile.exists()) { -// //try { -// //String filePath = utils.getBackgroundDir() + getBackgroundFileName(); -// preViewFilePath = _mSourceCroppedFilePath; -// LogUtils.d(TAG, String.format("preViewFilePathBackgroundView : %s", preViewFilePath)); -// bvPreviewBackground.previewBackgroundImage(preViewFilePath); -// /*Drawable drawable = FileUtils.getImageDrawable(filePath); -// if (drawable != null) { -// //drawable.setAlpha(120); -// //bvPreviewBackground.setImageDrawable(drawable); -// }*/ -// //ToastUtils.show("背景图片已更新"); -//// } catch (IOException e) { -//// LogUtils.d(TAG, e, Thread.currentThread().getStackTrace()); -//// ToastUtils.show("背景图片加载失败"); -//// } -// } else { -// ToastUtils.show("未使用背景图片"); -// preViewFilePath = ""; -// bvPreviewBackground.previewBackgroundImage(preViewFilePath); -//// Drawable drawable = getResources().getDrawable(R.drawable.blank10x10); -//// if (drawable != null) { -//// drawable.setAlpha(120); -//// bvPreviewBackground.setImageDrawable(drawable); -//// } -// } + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); + if (sourceFile == null) { + bvPreviewBackground.reloadCurrentBackground(); // 修复:调用正式背景刷新 + } else { + utils.saveFileToPreviewBean(sourceFile, sourceFileInfo); + bvPreviewBackground.reloadPreviewBackground(); // 修复:调用预览背景刷新 + } } // 点击事件监听器 @@ -230,10 +178,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg BackgroundBean bean = utils.getCurrentBackgroundBean(); bean.setIsUseBackgroundFile(false); utils.saveSettings(); - bvPreviewBackground.reloadPreviewBackground(); + bvPreviewBackground.reloadPreviewBackground(); // 修复:刷新预览 } }; + // 修复:选择图片后添加视图刷新逻辑 private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { @@ -295,35 +244,29 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - ToastUtils.show("onReceivedPictureClickListener not yet."); -// BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); -// utils.getCurrentBackgroundBean().setIsUseBackgroundFile(true); -// utils.saveSettings(); -// updateBackgroundView(); + ToastUtils.show("onReceivedPictureClickListener not yet."); } }; - private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() { + private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - // 从文件路径启动像素拾取活动 - //String imagePath = "/storage/emulated/0/DCIM/Camera/sample.jpg"; - String imagePath = _mSourceCroppedFile.toString(); - Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); - intent.putExtra("imagePath", imagePath); - startActivity(intent); - //App.getWinBoLLActivityManager().startWinBoLLActivity(getActivity(), intent, PixelPickerActivity.class); + // 从文件路径启动像素拾取活动 + String imagePath = _mSourceCroppedFile.toString(); + Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); + intent.putExtra("imagePath", imagePath); + startActivity(intent); } }; - private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() { + private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); - BackgroundBean bean = utils.getCurrentBackgroundBean(); - bean.setPixelColor(0); - utils.saveSettings(); - setBackgroundColor(); + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); + BackgroundBean bean = utils.getCurrentBackgroundBean(); + bean.setPixelColor(0); + utils.saveSettings(); + setBackgroundColor(); } }; @@ -333,7 +276,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg void compressQualityToRecivedPicture(Bitmap bitmap) { OutputStream outStream = null; try { - BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); + BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); File fRecivedPicture = new File(utils.getPreviewBackgroundScaledCompressFilePath()); if (!fRecivedPicture.exists()) { fRecivedPicture.createNewFile(); @@ -361,18 +304,18 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } /** - * 启动图片裁剪活动 + * 启动图片裁剪活动(核心修复:传递BackgroundView实际宽高比例,替代Bean默认值) * @param isCropFree 是否自由裁剪 */ public void startCropImageActivity(boolean isCropFree) { LogUtils.d(TAG, "startCropImageActivity"); - BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); - BackgroundBean bean = utils.getPreviewBackgroundBean(); - bean.setIsUseScaledCompress(true); - utils.saveSettings(); - - File fRecivedPicture = new File(utils.getPreviewBackgroundFilePath()); - + BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); + BackgroundBean bean = utils.getPreviewBackgroundBean(); + bean.setIsUseScaledCompress(true); + utils.saveSettings(); + + File fRecivedPicture = new File(utils.getPreviewBackgroundFilePath()); + Uri uri = UriUtil.getUriForFile(this, fRecivedPicture); LogUtils.d(TAG, "uri : " + uri.toString()); @@ -396,9 +339,28 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg intent.putExtra("crop", "true"); intent.putExtra("noFaceDetection", true); + // 修复核心:非自由裁剪时,传递bvPreviewBackground控件的实际宽高比例(确保裁剪与控件匹配) if (!isCropFree) { - intent.putExtra("aspectX", bean.getBackgroundWidth()); - intent.putExtra("aspectY", bean.getBackgroundHeight()); + // 1. 优先获取BackgroundView的实际宽高(控件已填充父视图,宽高=父容器尺寸) + int viewWidth = bvPreviewBackground.getWidth(); + int viewHeight = bvPreviewBackground.getHeight(); + + // 2. 容错处理:若控件未测量完成(宽/高为0),使用屏幕尺寸兜底 + if (viewWidth <= 0 || viewHeight <= 0) { + viewWidth = getResources().getDisplayMetrics().widthPixels; + viewHeight = getResources().getDisplayMetrics().heightPixels; + LogUtils.d(TAG, "控件未测量完成,使用屏幕尺寸作为裁剪比例:" + viewWidth + "x" + viewHeight); + } + + // 3. 计算宽高比的最大公约数,简化比例(避免过大数值导致裁剪工具不兼容,如1080:1920→9:16) + int gcd = calculateGCD(viewWidth, viewHeight); + int simplifiedWidth = viewWidth / gcd; + int simplifiedHeight = viewHeight / gcd; + + // 4. 传递简化后的宽高比例给裁剪意图(关键:确保裁剪比例与控件完全匹配) + intent.putExtra("aspectX", simplifiedWidth); + intent.putExtra("aspectY", simplifiedHeight); + LogUtils.d(TAG, "裁剪比例(控件实际比例/简化后):" + viewWidth + ":" + viewHeight + " → " + simplifiedWidth + ":" + simplifiedHeight); } intent.putExtra("return-data", true); @@ -411,84 +373,134 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } /** - * 保存剪裁后的Bitmap(优化版) + * 工具方法:计算两个数的最大公约数(用于简化宽高比) + * @param a 第一个数(宽) + * @param b 第二个数(高) + * @return 最大公约数 */ - private void saveCropBitmap(Bitmap bitmap) { - if (bitmap == null) { - ToastUtils.show("剪裁图片为空"); - return; + private int calculateGCD(int a, int b) { + if (b == 0) { + return a; } + return calculateGCD(b, a % b); + } - // 内存优化:大图片自动缩放 - Bitmap scaledBitmap = bitmap; - if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB - float scale = 1.0f; - while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) { - scale -= 0.2f; // 每次缩小20% - if (scale < 0.2f) break; // 最小缩放到20% - scaledBitmap = scaleBitmap(scaledBitmap, scale); - } - if (scaledBitmap != bitmap) { - bitmap.recycle(); // 回收原Bitmap - } - } + /** + * 保存剪裁后的Bitmap(优化版,修复裁剪后加载不到图片问题) + */ + private void saveCropBitmap(Bitmap bitmap) { + if (bitmap == null) { + ToastUtils.show("剪裁图片为空"); + // 修复:临时文件异常时也清理 + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "裁剪图片为空,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + return; + } - // 优化:创建保存目录 - File backgroundDir = new File(mBackgroundSourceUtils.getBackgroundSourceDirPath()); - if (!backgroundDir.exists()) { - if (!backgroundDir.mkdirs()) { - ToastUtils.show("无法创建保存目录"); - if (scaledBitmap != bitmap) scaledBitmap.recycle(); - return; - } - } + // 内存优化:大图片自动缩放(保持原逻辑) + Bitmap scaledBitmap = bitmap; + if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB + float scale = 1.0f; + while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) { + scale -= 0.2f; // 每次缩小20% + if (scale < 0.2f) break; // 最小缩放到20% + scaledBitmap = scaleBitmap(scaledBitmap, scale); + } + if (scaledBitmap != bitmap) { + bitmap.recycle(); // 回收原Bitmap + } + } - // 剪裁的图片的保存地址 - File fScaledCompressBitmapFile = new File(backgroundDir, BackgroundSourceUtils.getInstance(this).getPreviewBackgroundScaledCompressFilePath()); + // 优化:创建保存目录(保持原逻辑) + File backgroundDir = new File(mBackgroundSourceUtils.getBackgroundSourceDirPath()); + if (!backgroundDir.exists()) { + if (!backgroundDir.mkdirs()) { + ToastUtils.show("无法创建保存目录"); + if (scaledBitmap != bitmap) scaledBitmap.recycle(); + return; + } + } - // 优化:检查文件是否可写 - if (fScaledCompressBitmapFile.exists() && !fScaledCompressBitmapFile.canWrite()) { - if (!fScaledCompressBitmapFile.delete()) { - ToastUtils.show("无法删除旧文件"); - if (scaledBitmap != bitmap) scaledBitmap.recycle(); - return; - } - } + // 剪裁的图片的保存地址(保持原逻辑) + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); + String scaledCompressFileName = utils.getPreviewBackgroundScaledCompressFilePath(); + File fScaledCompressBitmapFile = new File(scaledCompressFileName); - FileOutputStream fos = null; - try { - fos = new FileOutputStream(fScaledCompressBitmapFile); - boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); - fos.flush(); - if (success) { + // 优化:检查文件是否可写(保持原逻辑) + if (fScaledCompressBitmapFile.exists() && !fScaledCompressBitmapFile.canWrite()) { + if (!fScaledCompressBitmapFile.delete()) { + ToastUtils.show("无法删除旧文件"); + if (scaledBitmap != bitmap) scaledBitmap.recycle(); + return; + } + } + + FileOutputStream fos = null; + try { + fos = new FileOutputStream(fScaledCompressBitmapFile); + boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); + fos.flush(); + if (success) { ToastUtils.show("图片压缩保存成功"); - BackgroundSourceUtils.getInstance(this).getPreviewBackgroundBean().setIsUseScaledCompress(true); - BackgroundSourceUtils.getInstance(this).saveSettings(); + BackgroundBean previewBean = utils.getPreviewBackgroundBean(); + // 修复1:同步裁剪后路径到预览Bean(关键!确保加载路径匹配) + // 从压缩文件路径中提取文件名,更新到previewBean的backgroundFileName + String cropFileName = fScaledCompressBitmapFile.getName(); + previewBean.setBackgroundFileName(cropFileName); // 重点:更新为裁剪后的压缩文件名 + // 修复2:强制设置isUseBackgroundFile=true(确保BackgroundView加载图片,而非透明背景) + previewBean.setIsUseBackgroundFile(true); + previewBean.setIsUseScaledCompress(true); + utils.saveSettings(); // 持久化保存Bean,确保路径同步 + + // 修复3:裁剪成功后立即清理临时文件(避免残留冲突) + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "裁剪成功,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + + // 修复:裁剪成功后刷新预览视图(保持原逻辑,此时路径已同步) bvPreviewBackground.reloadPreviewBackground(); - } else { - ToastUtils.show("图片压缩保存失败"); - BackgroundSourceUtils.getInstance(this).getPreviewBackgroundBean().setIsUseScaledCompress(false); - BackgroundSourceUtils.getInstance(this).saveSettings(); + } else { + ToastUtils.show("图片压缩保存失败"); + BackgroundBean previewBean = utils.getPreviewBackgroundBean(); + previewBean.setIsUseScaledCompress(false); + utils.saveSettings(); + // 修复:保存失败时清理临时文件 + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "裁剪失败,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + // 修复:保存失败时刷新原始预览图 bvPreviewBackground.reloadPreviewBackground(); - } - } catch (FileNotFoundException e) { - LogUtils.e(TAG, "文件未找到" + e); - ToastUtils.show("文件未找到" + e); - } catch (IOException e) { - LogUtils.e(TAG, "写入异常" + e); - ToastUtils.show("写入异常" + e); - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - LogUtils.e(TAG, "流关闭异常" + e); - ToastUtils.show("流关闭异常" + e); + } + } catch (FileNotFoundException e) { + LogUtils.e(TAG, "文件未找到" + e); + ToastUtils.show("文件未找到:" + e.getMessage()); + // 异常时清理临时文件 + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + } + } catch (IOException e) { + LogUtils.e(TAG, "写入异常" + e); + ToastUtils.show("写入异常:" + e.getMessage()); + // 异常时清理临时文件 + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + } + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LogUtils.e(TAG, "流关闭异常" + e); + ToastUtils.show("流关闭异常:" + e.getMessage()); } } if (scaledBitmap != null && !scaledBitmap.isRecycled()) { - scaledBitmap.recycle(); + scaledBitmap.recycle(); // 回收缩放后的Bitmap,避免内存泄漏 } } } @@ -509,8 +521,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg * 分享图片 */ void sharePicture() { - BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); - File fRecivedPicture = new File(utils.getCurrentBackgroundFilePath()); + BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); + File fRecivedPicture = new File(utils.getCurrentBackgroundFilePath()); Uri uri = UriUtil.getUriForFile(this, fRecivedPicture); Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.putExtra(Intent.EXTRA_STREAM, uri); @@ -519,95 +531,100 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg startActivity(Intent.createChooser(shareIntent, "Share Image")); } -// public File getRecivedPictureFile() { -// BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); -// utils.loadSettings(); -// return new File(utils.getBackgroundSourceDirPath(), _RecivedBackgroundFileName); -// } + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_SELECT_PICTURE && resultCode == RESULT_OK) { + try { + Uri selectedImage = data.getData(); + LogUtils.d(TAG, "Uri is : " + selectedImage.toString()); + File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage)); + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); -// public void saveToRecivedBackground(String srcFilePath, String srcFillSourcePath) { -// BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); -// utils.loadSettings(); -// File dstFile = new File(utils.getBackgroundSourceDirPath(), _RecivedBackgroundFileName); -// //compressQualityToRecivedPicture(srcFilePath); -// ToastUtils.show("compressQualityToRecivedPicture not yet."); -// FileUtils.copyFile(new File(srcFilePath), dstFile); -// } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_SELECT_PICTURE && resultCode == RESULT_OK) { - try { - Uri selectedImage = data.getData(); - LogUtils.d(TAG, "Uri is : " + selectedImage.toString()); - File fSrcImage = new File(UriUtil.getFilePathFromUri(this, selectedImage)); - BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); + // 修复:保存图片到预览Bean后,立即刷新BackgroundView显示预览图 utils.saveFileToPreviewBean(fSrcImage, selectedImage.toString()); - startCropImageActivity(false); - //mfRecivedPicture = getRecivedPictureFile(); -// BackgroundBean bean = utils.getPreviewBackgroundBean(); -// mfRecivedPicture = getRecivedPictureFile(); -// if (FileUtils.copyFile(fSrcImage, mfRecivedPicture)) { -// startCropImageActivity(false); -// } else { -// ToastUtils.show("图片复制失败,请重试"); -// } - } catch (Exception e) { - LogUtils.e(TAG, "选择图片异常" + e); - ToastUtils.show("选择图片失败:" + e.getMessage()); - } - } else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) { - LogUtils.d(TAG, "REQUEST_TAKE_PHOTO"); - Bundle extras = data.getExtras(); - if (extras != null) { - Bitmap imageBitmap = (Bitmap) extras.get("data"); - if (imageBitmap != null) { - compressQualityToRecivedPicture(imageBitmap); - startCropImageActivity(false); - } else { - ToastUtils.show("拍照图片为空"); - } - } else { - ToastUtils.show("拍照数据获取失败"); - } - } else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) { - LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE"); - try { - Bitmap cropBitmap = null; - // 方案1:通过Intent获取剪裁后的Bitmap - if (data != null && data.hasExtra("data")) { - cropBitmap = data.getParcelableExtra("data"); - } else if (_mSourceCropTempFile.exists()) { - LogUtils.d(TAG, String.format("_mSourceCropTempFile Exists, Path is :%s ", _mSourceCropTempFile.getPath())); - cropBitmap = BitmapFactory.decodeFile(_mSourceCropTempFile.getPath()); - } else { - ToastUtils.show("剪裁文件不存在"); - return; - } + bvPreviewBackground.reloadPreviewBackground(); - if (cropBitmap != null) { - saveCropBitmap(cropBitmap); - } else { - ToastUtils.show("获取剪裁图片失败"); - } - } catch (OutOfMemoryError e) { - LogUtils.e(TAG, "内存溢出" + e); - ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片"); - } catch (Exception e) { - LogUtils.e(TAG, "剪裁保存异常" + e); - ToastUtils.show("保存失败:" + e.getMessage()); - }/* finally { - // 安全删除临时文件 - if (mfTempCropPicture.exists()) { - mfTempCropPicture.delete(); - } - }*/ - } else if (resultCode != RESULT_OK) { - LogUtils.d(TAG, "操作取消或失败,requestCode: " + requestCode); - ToastUtils.show("操作已取消"); - } - } + // 启动裁剪(保持原逻辑,裁剪比例已修复) + startCropImageActivity(false); + } catch (Exception e) { + LogUtils.e(TAG, "选择图片异常" + e); + ToastUtils.show("选择图片失败:" + e.getMessage()); + // 异常时清理临时文件(避免残留) + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "选择图片异常,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + } + } else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) { + LogUtils.d(TAG, "REQUEST_TAKE_PHOTO"); + Bundle extras = data.getExtras(); + if (extras != null) { + Bitmap imageBitmap = (Bitmap) extras.get("data"); + if (imageBitmap != null) { + compressQualityToRecivedPicture(imageBitmap); + // 修复:拍照压缩后,刷新预览图 + bvPreviewBackground.reloadPreviewBackground(); + startCropImageActivity(false); + } else { + ToastUtils.show("拍照图片为空"); + // 图片为空时清理临时文件 + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "拍照图片为空,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + } + } else { + ToastUtils.show("拍照数据获取失败"); + // 数据获取失败时清理临时文件 + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "拍照数据获取失败,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + } + } else if (requestCode == REQUEST_CROP_IMAGE && resultCode == RESULT_OK) { + LogUtils.d(TAG, "CROP_IMAGE_REQUEST_CODE"); + try { + Bitmap cropBitmap = null; + // 方案1:通过Intent获取剪裁后的Bitmap + if (data != null && data.hasExtra("data")) { + cropBitmap = data.getParcelableExtra("data"); + } else if (_mSourceCropTempFile.exists()) { + LogUtils.d(TAG, String.format("_mSourceCropTempFile Exists, Path is :%s ", _mSourceCropTempFile.getPath())); + cropBitmap = BitmapFactory.decodeFile(_mSourceCropTempFile.getPath()); + } else { + ToastUtils.show("剪裁文件不存在"); + return; + } + + if (cropBitmap != null) { + saveCropBitmap(cropBitmap); // 调用保存方法(内含预览刷新+临时文件清理) + } else { + ToastUtils.show("获取剪裁图片失败"); + } + } catch (OutOfMemoryError e) { + LogUtils.e(TAG, "内存溢出" + e); + ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片"); + } catch (Exception e) { + LogUtils.e(TAG, "剪裁保存异常" + e); + ToastUtils.show("保存失败:" + e.getMessage()); + } finally { + // 修复核心:裁剪流程结束后,强制清理临时文件(双重保障,避免残留冲突) + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "裁剪流程结束,强制清理临时文件:" + _mSourceCropTempFile.getPath()); + } + } + } else if (resultCode != RESULT_OK) { + LogUtils.d(TAG, "操作取消或失败,requestCode: " + requestCode); + ToastUtils.show("操作已取消"); + // 修复:操作取消/失败时,强制清理临时文件(避免影响下次裁剪) + if (_mSourceCropTempFile.exists()) { + _mSourceCropTempFile.delete(); + LogUtils.d(TAG, "操作取消/失败,清理临时文件:" + _mSourceCropTempFile.getPath()); + } + } + } /** * 检查类型是否为图片 @@ -645,26 +662,25 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg } } - void setBackgroundColor() { - BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); - BackgroundBean bean = utils.getCurrentBackgroundBean(); - int nPixelColor = bean.getPixelColor(); - RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1); - mainLayout.setBackgroundColor(nPixelColor); - } + void setBackgroundColor() { + BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); + BackgroundBean bean = utils.getCurrentBackgroundBean(); + int nPixelColor = bean.getPixelColor(); + RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1); + mainLayout.setBackgroundColor(nPixelColor); + } - @Override - protected void onResume() { - super.onResume(); - setBackgroundColor(); - } + @Override + protected void onResume() { + super.onResume(); + setBackgroundColor(); + } - public void onNetworkBackgroundDialog(View view) { - // 在需要显示对话框的地方(如网络状态监听回调中) - NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() { + public void onNetworkBackgroundDialog(View view) { + // 在需要显示对话框的地方(如网络状态监听回调中) + NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() { @Override public void onConfirm(String szConfirmFilePath, String szConfirmFileUrl) { - //ToastUtils.show("onConfirm"); // 保存预览资源信息 preViewFilePath = szConfirmFilePath; preViewFileUrl = szConfirmFileUrl; @@ -673,31 +689,33 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg @Override public void onCancel() { - //ToastUtils.show("onCancel"); + // 取消逻辑 } }); - // 可选:修改对话框标题和内容(适配自定义场景) - dialog.setTitle("网络图片下载对话框"); - dialog.setContent("是否下载地址中的图片资源,作为应用背景图片?"); + // 可选:修改对话框标题和内容(适配自定义场景) + dialog.setTitle("网络图片下载对话框"); + dialog.setContent("是否下载地址中的图片资源,作为应用背景图片?"); - // 显示对话框 - dialog.show(); + // 显示对话框 + dialog.show(); - } + } - interface OnRecivedPictureListener { - void onRecivedPicture(String srcFilePath, String srcFileUrl); - } + interface OnRecivedPictureListener { + void onRecivedPicture(String srcFilePath, String srcFileUrl); + } - OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){ - @Override - public void onRecivedPicture(String srcFilePath, String srcFileUrl) { - BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); - utils.saveFileToPreviewBean(new File(srcFilePath), srcFileUrl); - startCropImageActivity(true); - } - }; + OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){ + @Override + public void onRecivedPicture(String srcFilePath, String srcFileUrl) { + BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); + utils.saveFileToPreviewBean(new File(srcFilePath), srcFileUrl); + // 修复:网络图片下载后刷新预览 + bvPreviewBackground.reloadPreviewBackground(); + startCropImageActivity(true); + } + }; /** * 重写finish方法,确保所有退出场景都触发Toast @@ -705,7 +723,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg @Override public void finish() { if (!isCommitSettings) { - YesNoAlertDialog.show(this, "应用背景更改提示:", "是否应用预览图片?", new YesNoAlertDialog.OnDialogResultListener(){ + YesNoAlertDialog.show(this, "应用背景更改提示:", "是否应用预览图片?", new YesNoAlertDialog.OnDialogResultListener(){ @Override public void onNo() { @@ -725,9 +743,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg finish(); } }); - } else { - super.finish(); - } + } else { + super.finish(); + } } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/model/BackgroundBean.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/model/BackgroundBean.java index 4f1f0025..44e8988b 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/model/BackgroundBean.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/model/BackgroundBean.java @@ -1,143 +1,172 @@ package cc.winboll.studio.powerbell.model; -/** - * @Author ZhanGSKen - * @Date 2024/07/18 11:52:28 - * @Describe 应用背景图片数据类 - */ import android.util.JsonReader; import android.util.JsonWriter; import cc.winboll.studio.libappbase.BaseBean; import java.io.IOException; +/** + * @Author ZhanGSKen + * @Date 2024/07/18 11:52:28 + * @Describe 应用背景图片数据类(存储正式/预览背景配置,支持JSON序列化/反序列化) + */ public class BackgroundBean extends BaseBean { public static final String TAG = "BackgroundPictureBean"; - String backgroundFileName = ""; - String backgroundFileInfo = ""; - boolean isUseBackgroundFile = false; - String backgroundScaledCompressFileName = ""; - boolean isUseScaledCompress = false; - int backgroundWidth = 100; - int backgroundHeight = 100; - // 图片拾取像素颜色 - int pixelColor = 0; + // 核心字段:背景图片文件名(对应BackgroundSource目录下的图片文件) + private String backgroundFileName = ""; + // 附加字段:图片信息(如Uri、网络地址等,仅作备注,不参与路径生成) + private String backgroundFileInfo = ""; + // 控制字段:是否启用背景图片(true-显示背景图,false-显示透明背景) + private boolean isUseBackgroundFile = false; + // 核心字段:压缩后背景图片文件名(对应BackgroundSource目录下的压缩图片) + private String backgroundScaledCompressFileName = ""; + // 控制字段:是否启用压缩背景图(true-加载压缩图,false-加载原图) + private boolean isUseScaledCompress = false; + // 裁剪比例字段:背景图宽高比(默认1:1,用于固定比例裁剪) + private int backgroundWidth = 100; + private int backgroundHeight = 100; + // 像素拾取字段:拾取的像素颜色(用于纯色背景) + private int pixelColor = 0; + /** + * 无参构造器(必须,JSON反序列化时需默认构造器) + */ public BackgroundBean() { } - public void setBackgroundScaledCompressFileName(String backgroundScaledCompressFileName) { - this.backgroundScaledCompressFileName = backgroundScaledCompressFileName; - } + // ====================================== Getter/Setter 方法(全字段,确保序列化/反序列化完整)====================================== + public String getBackgroundFileName() { + return backgroundFileName; + } - public String getBackgroundScaledCompressFileName() { - return backgroundScaledCompressFileName; - } + public void setBackgroundFileName(String backgroundFileName) { + this.backgroundFileName = backgroundFileName == null ? "" : backgroundFileName; // 防null,避免空指针 + } - public void setIsUseScaledCompress(boolean isUseScaledCompress) { - this.isUseScaledCompress = isUseScaledCompress; - } + public String getBackgroundFileInfo() { + return backgroundFileInfo; + } - public boolean isUseScaledCompress() { - return isUseScaledCompress; - } + public void setBackgroundFileInfo(String backgroundFileInfo) { + this.backgroundFileInfo = backgroundFileInfo == null ? "" : backgroundFileInfo; // 防null,避免空指针 + } - public void setIsUseBackgroundFile(boolean isUseBackgroundFile) { - this.isUseBackgroundFile = isUseBackgroundFile; - } + public boolean isUseBackgroundFile() { + return isUseBackgroundFile; + } - public boolean isUseBackgroundFile() { - return isUseBackgroundFile; - } + public void setIsUseBackgroundFile(boolean isUseBackgroundFile) { + this.isUseBackgroundFile = isUseBackgroundFile; + } - public void setBackgroundFileInfo(String backgroundFileInfo) { - this.backgroundFileInfo = backgroundFileInfo; - } + public String getBackgroundScaledCompressFileName() { + return backgroundScaledCompressFileName; + } - public String getBackgroundFileInfo() { - return backgroundFileInfo; - } + public void setBackgroundScaledCompressFileName(String backgroundScaledCompressFileName) { + this.backgroundScaledCompressFileName = backgroundScaledCompressFileName == null ? "" : backgroundScaledCompressFileName; // 防null + } - public void setBackgroundFileName(String backgroundFileName) { - this.backgroundFileName = backgroundFileName; - } + public boolean isUseScaledCompress() { + return isUseScaledCompress; + } - public String getBackgroundFileName() { - return backgroundFileName; - } - - public void setPixelColor(int pixelColor) { - this.pixelColor = pixelColor; - } - - public int getPixelColor() { - return pixelColor; - } - - public void setBackgroundWidth(int backgroundWidth) { - this.backgroundWidth = backgroundWidth; + public void setIsUseScaledCompress(boolean isUseScaledCompress) { + this.isUseScaledCompress = isUseScaledCompress; } public int getBackgroundWidth() { return backgroundWidth; } - public void setBackgroundHeight(int backgroundHeight) { - this.backgroundHeight = backgroundHeight; + public void setBackgroundWidth(int backgroundWidth) { + this.backgroundWidth = backgroundWidth <= 0 ? 100 : backgroundWidth; // 防无效值,确保宽高比有效 } public int getBackgroundHeight() { return backgroundHeight; } - @Override - public String getName() { - return BackgroundBean.class.getName(); + public void setBackgroundHeight(int backgroundHeight) { + this.backgroundHeight = backgroundHeight <= 0 ? 100 : backgroundHeight; // 防无效值,确保宽高比有效 } + public int getPixelColor() { + return pixelColor; + } + + public void setPixelColor(int pixelColor) { + this.pixelColor = pixelColor; + } + + // ====================================== 序列化/反序列化方法(适配JSON读写,确保数据持久化完整)====================================== + @Override + public String getName() { + return BackgroundBean.class.getName(); // 必须重写,BaseBean序列化时需类名标识 + } + + /** + * 将Bean数据写入JSON(序列化,保存到文件) + * 确保所有字段都被写入,无遗漏 + */ @Override public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { super.writeThisToJsonWriter(jsonWriter); BackgroundBean bean = this; jsonWriter.name("backgroundFileName").value(bean.getBackgroundFileName()); - jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo()); - jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile()); - jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName()); - jsonWriter.name("isUseScaledCompress").value(bean.isUseScaledCompress()); - jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth()); + jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo()); + jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile()); + jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName()); + jsonWriter.name("isUseScaledCompress").value(bean.isUseScaledCompress()); + jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth()); jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight()); jsonWriter.name("pixelColor").value(bean.getPixelColor()); } + /** + * 从JSON读取数据到Bean(反序列化,从文件加载) + * 确保所有字段都被读取,兼容旧版本(无字段时设默认值) + */ @Override public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { BackgroundBean bean = new BackgroundBean(); jsonReader.beginObject(); while (jsonReader.hasNext()) { String name = jsonReader.nextName(); - if (name.equals("backgroundFileName")) { - bean.setBackgroundFileName(jsonReader.nextString()); - } else if (name.equals("backgroundFileInfo")) { - bean.setBackgroundFileInfo(jsonReader.nextString()); - } else if (name.equals("isUseBackgroundFile")) { - bean.setIsUseBackgroundFile(jsonReader.nextBoolean()); - } else if (name.equals("backgroundScaledCompressFileName")) { - bean.setBackgroundScaledCompressFileName(jsonReader.nextString()); - } else if (name.equals("isUseScaledCompress")) { - bean.setIsUseScaledCompress(jsonReader.nextBoolean()); - } else if (name.equals("backgroundWidth")) { - bean.setBackgroundWidth(jsonReader.nextInt()); - } else if (name.equals("backgroundHeight")) { - bean.setBackgroundHeight(jsonReader.nextInt()); - } else if (name.equals("pixelColor")) { - bean.setPixelColor(jsonReader.nextInt()); - } else { - jsonReader.skipValue(); + switch (name) { + case "backgroundFileName": + bean.setBackgroundFileName(jsonReader.nextString()); + break; + case "backgroundFileInfo": + bean.setBackgroundFileInfo(jsonReader.nextString()); + break; + case "isUseBackgroundFile": + bean.setIsUseBackgroundFile(jsonReader.nextBoolean()); + break; + case "backgroundScaledCompressFileName": + bean.setBackgroundScaledCompressFileName(jsonReader.nextString()); + break; + case "isUseScaledCompress": + bean.setIsUseScaledCompress(jsonReader.nextBoolean()); + break; + case "backgroundWidth": + bean.setBackgroundWidth(jsonReader.nextInt()); + break; + case "backgroundHeight": + bean.setBackgroundHeight(jsonReader.nextInt()); + break; + case "pixelColor": + bean.setPixelColor(jsonReader.nextInt()); + break; + default: + jsonReader.skipValue(); // 跳过未知字段,兼容旧版本Bean + break; } } - // 结束 JSON 对象 jsonReader.endObject(); return bean; } } + diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java index eb870047..f187e732 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/BackgroundSourceUtils.java @@ -3,52 +3,48 @@ package cc.winboll.studio.powerbell.utils; import android.content.Context; import cc.winboll.studio.powerbell.model.BackgroundBean; import java.io.File; -import java.util.UUID; +import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.ToastUtils; /** * @Author ZhanGSKen * @Date 2024/07/18 12:07:20 - * @Describe 背景图片工具集(修复单例模式,线程安全) + * @Describe 背景图片工具集(修复单例模式,线程安全+数据流转正常) */ public class BackgroundSourceUtils { public static final String TAG = "BackgroundPictureUtils"; - // 1. 静态实例加volatile,禁止指令重排,保证可见性 + // 1. 静态实例加volatile,禁止指令重排,保证可见性(双重校验锁单例核心) private static volatile BackgroundSourceUtils sInstance; private Context mContext; - private File currentBackgroundBeanFile; + private File currentBackgroundBeanFile; private BackgroundBean currentBackgroundBean; - private File previewBackgroundBeanFile; + private File previewBackgroundBeanFile; private BackgroundBean previewBackgroundBean; - // 应用外部存储文件夹路径 - private File fUtilsDir; - private File fModelDir; - // 背景图片目录 + // 应用外部存储文件夹路径(按功能划分目录,避免路径混乱) + private File fUtilsDir; + private File fModelDir; + // 背景图片源文件目录(存储正式/预览图片) private File fBackgroundSourceDir; - // 2. 私有构造器(加防反射逻辑) + // 2. 私有构造器(加防反射逻辑+初始化目录) private BackgroundSourceUtils(Context context) { - // 防反射破坏:若已有实例,抛异常阻止创建 + // 防反射破坏:若已有实例,抛异常阻止重复创建 if (sInstance != null) { throw new RuntimeException("BackgroundSourceUtils 是单例类,禁止重复创建!"); } - // 上下文建议用Application Context,避免内存泄漏 + // 上下文用Application Context,避免Activity内存泄漏 this.mContext = context.getApplicationContext(); - fUtilsDir = this.mContext.getExternalFilesDir(TAG); - fModelDir = new File(fUtilsDir, "ModelDir"); - currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json"); - previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json"); - fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource"); - - // 加载配置 + // 初始化目录(按功能划分,确保目录存在) + initDirs(); + // 加载配置(正式/预览Bean) loadSettings(); } - // 3. 双重校验锁单例(线程安全,高效) + // 3. 双重校验锁单例(线程安全,高效,支持多线程并发调用) public static BackgroundSourceUtils getInstance(Context context) { - // 第一重校验:避免每次调用都加锁(提高效率) + // 第一重校验:避免每次调用都加锁(提升效率) if (sInstance == null) { // 同步锁:保证同一时刻只有一个线程进入创建逻辑 synchronized (BackgroundSourceUtils.class) { @@ -61,84 +57,202 @@ public class BackgroundSourceUtils { return sInstance; } - /* - * 加载背景图片配置数据 - */ + /** + * 初始化所有目录(确保目录存在,避免文件操作失败) + */ + private void initDirs() { + // 工具类根目录(外部存储:/Android/data/包名/files/BackgroundPictureUtils) + fUtilsDir = mContext.getExternalFilesDir(TAG); + if (fUtilsDir == null) { + LogUtils.e(TAG, "外部存储不可用,无法初始化目录"); + return; + } + // 模型文件目录(存储BackgroundBean的JSON文件) + fModelDir = new File(fUtilsDir, "ModelDir"); + // 背景图片源目录(存储正式/预览图片文件) + fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource"); + + // 递归创建所有目录(确保目录存在) + if (!fModelDir.exists()) { + fModelDir.mkdirs(); + LogUtils.d(TAG, "创建模型文件目录:" + fModelDir.getAbsolutePath()); + } + if (!fBackgroundSourceDir.exists()) { + fBackgroundSourceDir.mkdirs(); + LogUtils.d(TAG, "创建背景图片目录:" + fBackgroundSourceDir.getAbsolutePath()); + } + + // 初始化Bean文件对象(存储JSON配置) + currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json"); + previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json"); + } + + /** + * 加载背景图片配置数据(正式/预览Bean) + * 从JSON文件读取,若文件不存在则创建默认Bean并保存 + */ void loadSettings() { + // 加载正式Bean currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class); if (currentBackgroundBean == null) { currentBackgroundBean = new BackgroundBean(); BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); + LogUtils.d(TAG, "正式背景Bean不存在,创建默认Bean"); } - previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class); + + // 加载预览Bean + previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class); if (previewBackgroundBean == null) { previewBackgroundBean = new BackgroundBean(); BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); + LogUtils.d(TAG, "预览背景Bean不存在,创建默认Bean"); } } - - public BackgroundBean getCurrentBackgroundBean() { - return currentBackgroundBean; - } - - public BackgroundBean getPreviewBackgroundBean() { - return previewBackgroundBean; - } - public String getCurrentBackgroundFilePath() { - loadSettings(); - File file = new File(fBackgroundSourceDir, currentBackgroundBean.getBackgroundFileName()); - return file.getAbsolutePath(); - } - - public String getPreviewBackgroundFilePath() { - loadSettings(); - File file = new File(fBackgroundSourceDir, previewBackgroundBean.getBackgroundFileName()); - return file.getAbsolutePath(); - } - - public String getPreviewBackgroundScaledCompressFilePath() { - loadSettings(); - File file = new File(fBackgroundSourceDir, previewBackgroundBean.getBackgroundScaledCompressFileName()); - return file.getAbsolutePath(); - } - - public void saveSettings() { - BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); - BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); + /** + * 获取正式背景Bean(对外提供,用于修改正式配置) + */ + public BackgroundBean getCurrentBackgroundBean() { + return currentBackgroundBean; } - + + /** + * 获取预览背景Bean(对外提供,用于修改预览配置) + */ + public BackgroundBean getPreviewBackgroundBean() { + return previewBackgroundBean; + } + + /** + * 获取正式背景图片路径(拼接:背景目录+正式Bean中的文件名) + */ + public String getCurrentBackgroundFilePath() { + loadSettings(); // 加载最新配置,避免数据滞后 + File file = new File(fBackgroundSourceDir, currentBackgroundBean.getBackgroundFileName()); + LogUtils.d(TAG, "正式背景路径:" + file.getAbsolutePath()); + return file.getAbsolutePath(); + } + + /** + * 获取预览背景图片路径(拼接:背景目录+预览Bean中的文件名) + */ + public String getPreviewBackgroundFilePath() { + loadSettings(); // 加载最新配置,避免数据滞后 + File file = new File(fBackgroundSourceDir, previewBackgroundBean.getBackgroundFileName()); + LogUtils.d(TAG, "预览背景路径:" + file.getAbsolutePath()); + return file.getAbsolutePath(); + } + + /** + * 获取预览背景压缩图片路径(拼接:背景目录+预览Bean中的压缩文件名) + */ + public String getPreviewBackgroundScaledCompressFilePath() { + loadSettings(); // 加载最新配置,避免数据滞后 + File file = new File(fBackgroundSourceDir, previewBackgroundBean.getBackgroundScaledCompressFileName()); + LogUtils.d(TAG, "预览压缩背景路径:" + file.getAbsolutePath()); + return file.getAbsolutePath(); + } + + /** + * 保存配置(将正式/预览Bean同步到JSON文件,持久化存储) + */ + public void saveSettings() { + BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); + BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); + LogUtils.d(TAG, "配置保存成功:正式Bean=" + currentBackgroundBeanFile.getAbsolutePath() + ",预览Bean=" + previewBackgroundBeanFile.getAbsolutePath()); + } + + /** + * 获取背景图片源目录路径(对外提供,用于创建临时文件) + */ public String getBackgroundSourceDirPath() { return fBackgroundSourceDir.getAbsolutePath(); } - - /* - * 保存图片到预览模型, 并返回预览模型数据 - */ - public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) { - File previewBackgroundFile = new File(fBackgroundSourceDir, FileUtils.createUniqueFileName(sourceFile)); - //ToastUtils.show(String.format("saveFileToPreviewBean previewBackgroundFile : %s", previewBackgroundFile.getAbsolutePath())); - - FileUtils.copyFile(sourceFile, previewBackgroundFile); - previewBackgroundBean = new BackgroundBean(); - previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); - previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_"+previewBackgroundFile.getName()); - previewBackgroundBean.setBackgroundFileName(fileInfo); - saveSettings(); - - ToastUtils.show(String.format("saveFileToPreviewBean getPreviewBackgroundFilePath() : %s", getPreviewBackgroundFilePath())); - - return previewBackgroundBean; - } - - public void commitPreviewSourceToCurrent() { - currentBackgroundBean = previewBackgroundBean; - saveSettings(); - } - - public void setCurrentSourceToPreview() { - previewBackgroundBean = currentBackgroundBean; - saveSettings(); - } + + /** + * 保存图片到预览Bean(核心修复:解决文件名覆盖+路径无效问题) + * @param sourceFile 源图片文件(非空,必须存在) + * @param fileInfo 图片附加信息(如Uri字符串,仅作备注) + * @return 更新后的预览Bean + */ + public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) { + // 校验源文件合法性 + if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) { + LogUtils.e(TAG, "源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null")); + ToastUtils.show("源图片文件无效"); + return previewBackgroundBean; + } + + // 确保背景目录存在(防止首次使用时目录未创建) + if (!fBackgroundSourceDir.exists()) { + fBackgroundSourceDir.mkdirs(); + LogUtils.d(TAG, "背景目录不存在,自动创建:" + fBackgroundSourceDir.getAbsolutePath()); + } + + // 生成唯一文件名(基于源文件后缀,避免重复) + String uniqueFileName = FileUtils.createUniqueFileName(sourceFile); + File previewBackgroundFile = new File(fBackgroundSourceDir, uniqueFileName); + + // 复制源文件到预览目录(确保图片实际保存成功) + boolean copySuccess = FileUtils.copyFile(sourceFile, previewBackgroundFile); + if (!copySuccess) { + LogUtils.e(TAG, "图片复制到预览目录失败:" + sourceFile.getAbsolutePath() + " → " + previewBackgroundFile.getAbsolutePath()); + ToastUtils.show("预览图片保存失败"); + return previewBackgroundBean; + } + + // 正确赋值预览Bean(核心修复:不覆盖文件名,将附加信息存入backgroundFileInfo) + previewBackgroundBean = new BackgroundBean(); + previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); // 正确赋值:唯一文件名 + previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_" + previewBackgroundFile.getName()); // 压缩文件名(前缀标识) + previewBackgroundBean.setBackgroundFileInfo(fileInfo); // 正确赋值:附加信息(Uri) + previewBackgroundBean.setIsUseBackgroundFile(true); // 标记使用背景图,确保BackgroundView加载 + previewBackgroundBean.setBackgroundWidth(100); // 默认宽高比1:1(可根据需求调整) + previewBackgroundBean.setBackgroundHeight(100); + saveSettings(); // 持久化保存预览Bean + + LogUtils.d(TAG, "预览图片保存成功:" + previewBackgroundFile.getAbsolutePath()); + ToastUtils.show("预览图片加载成功"); + return previewBackgroundBean; + } + + /** + * 提交预览背景到正式背景(将预览Bean深拷贝到正式Bean,应用预览配置) + */ + public void commitPreviewSourceToCurrent() { + // 深拷贝:新建正式Bean,复制预览Bean的所有字段(避免浅拷贝导致数据污染) + currentBackgroundBean = new BackgroundBean(); + currentBackgroundBean.setBackgroundFileName(previewBackgroundBean.getBackgroundFileName()); + currentBackgroundBean.setBackgroundFileInfo(previewBackgroundBean.getBackgroundFileInfo()); + currentBackgroundBean.setIsUseBackgroundFile(previewBackgroundBean.isUseBackgroundFile()); + currentBackgroundBean.setBackgroundScaledCompressFileName(previewBackgroundBean.getBackgroundScaledCompressFileName()); + currentBackgroundBean.setIsUseScaledCompress(previewBackgroundBean.isUseScaledCompress()); + currentBackgroundBean.setBackgroundWidth(previewBackgroundBean.getBackgroundWidth()); + currentBackgroundBean.setBackgroundHeight(previewBackgroundBean.getBackgroundHeight()); + currentBackgroundBean.setPixelColor(previewBackgroundBean.getPixelColor()); + + saveSettings(); // 持久化保存正式Bean + LogUtils.d(TAG, "预览背景提交成功:正式背景更新为预览背景"); + ToastUtils.show("背景图片应用成功"); + } + + /** + * 将正式背景同步到预览背景(深拷贝,初始化预览为当前正式背景) + */ + public void setCurrentSourceToPreview() { + // 深拷贝:新建预览Bean,复制正式Bean的所有字段(避免浅拷贝导致数据污染) + previewBackgroundBean = new BackgroundBean(); + previewBackgroundBean.setBackgroundFileName(currentBackgroundBean.getBackgroundFileName()); + previewBackgroundBean.setBackgroundFileInfo(currentBackgroundBean.getBackgroundFileInfo()); + previewBackgroundBean.setIsUseBackgroundFile(currentBackgroundBean.isUseBackgroundFile()); + previewBackgroundBean.setBackgroundScaledCompressFileName(currentBackgroundBean.getBackgroundScaledCompressFileName()); + previewBackgroundBean.setIsUseScaledCompress(currentBackgroundBean.isUseScaledCompress()); + previewBackgroundBean.setBackgroundWidth(currentBackgroundBean.getBackgroundWidth()); + previewBackgroundBean.setBackgroundHeight(currentBackgroundBean.getBackgroundHeight()); + previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor()); + + saveSettings(); // 持久化保存预览Bean + LogUtils.d(TAG, "正式背景同步到预览:预览背景更新为当前正式背景"); + } } 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 b77543b2..5ef48df7 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 @@ -10,33 +10,28 @@ import android.util.AttributeSet; import android.widget.ImageView; import android.widget.RelativeLayout; import cc.winboll.studio.libappbase.LogUtils; -import cc.winboll.studio.powerbell.R; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; +import cc.winboll.studio.powerbell.model.BackgroundBean; import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils; -import cc.winboll.studio.libappbase.ToastUtils; +import java.io.File; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/11/19 18:01 - * @Describe 背景图片视图控件(全透明背景 + 不拉伸居中平铺 + 完全填充父视图) + * @Describe 背景图片视图控件(全透明背景 + 不拉伸居中平铺 + 完全填充父视图 + 预览/正式模式切换) */ public class BackgroundView extends RelativeLayout { public static final String TAG = "BackgroundView"; - Context mContext; + private Context mContext; private ImageView ivBackground; + private BackgroundSourceUtils backgroundSourceUtils; // 工具类实例(避免重复创建) - private static String BACKGROUND_IMAGE_FOLDER = "Background"; - private static String BACKGROUND_IMAGE_FILENAME = "current.data"; - private static String BACKGROUND_IMAGE_PREVIEW_FILENAME = "current_preview.data"; - private static String backgroundSourceFilePath; private float imageAspectRatio = 1.0f; // 图片原始宽高比(控制不拉伸) - // 标记当前是否处于预览模式 + // 标记当前是否处于预览模式(用于区分加载预览/正式背景) private boolean isPreviewMode = false; + // 构造器(兼容所有布局场景) public BackgroundView(Context context) { super(context); this.mContext = context; @@ -61,223 +56,134 @@ public class BackgroundView extends RelativeLayout { initView(); } - void initView() { - // 1. 控件本身:完全填充父视图 + 全透明背景 + 无内边距 + /** + * 初始化视图(控件本身+内部ImageView) + */ + private void initView() { + // 1. 控件本身配置:完全填充父视图 + 全透明背景 + 无内边距 setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - setPadding(0, 0, 0, 0); // 取消自身内边距 - setBackgroundColor(0x00000000); // 全透明背景(#00000000) - setBackground(new ColorDrawable(0x00000000)); // 双重保障:同时设置Background为透明(兼容低版本) + setPadding(0, 0, 0, 0); // 取消自身内边距(避免父容器与控件间缝隙) + setBackgroundColor(0x00000000); // 全透明背景(ARGB:透明+黑色,无视觉影响) + setBackground(new ColorDrawable(0x00000000)); // 双重保障:兼容Android低版本,确保背景透明 - initBackgroundImageView(); // 初始化内部ImageView(全透明 + 不拉伸居中平铺) + // 初始化工具类(单例,全局唯一) + backgroundSourceUtils = BackgroundSourceUtils.getInstance(mContext); - backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getCurrentBackgroundFilePath(); - loadAndSetImageViewBackground(); + // 2. 初始化内部ImageView(核心:不拉伸+居中平铺+全透明) + initBackgroundImageView(); + + // 3. 初始加载:默认加载正式背景 + reloadCurrentBackground(); } + /** + * 初始化内部ImageView(配置尺寸、缩放模式、背景等) + */ private void initBackgroundImageView() { ivBackground = new ImageView(mContext); - // 2. ImageView:初始宽高WRAP_CONTENT + 居中 + 无内边距 + 全透明背景 + // 基础布局:宽高WRAP_CONTENT(跟随图片比例)+ 居中显示 + 无内边距/边距 RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); - layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); // 居中显示 - layoutParams.setMargins(0, 0, 0, 0); // 取消边距 + layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); // 核心:ImageView在控件中居中 + layoutParams.setMargins(0, 0, 0, 0); // 取消边距(避免ImageView与控件间缝隙) ivBackground.setLayoutParams(layoutParams); - // 3. 缩放模式:FIT_CENTER(不拉伸,按比例显示) + // 关键配置:缩放模式FIT_CENTER(不拉伸,按原始比例显示) ivBackground.setScaleType(ImageView.ScaleType.FIT_CENTER); - ivBackground.setPadding(0, 0, 0, 0); // 取消内部padding + ivBackground.setPadding(0, 0, 0, 0); // 取消内部padding(避免图片与ImageView间缝隙) ivBackground.setBackgroundColor(0x00000000); // ImageView背景全透明 - ivBackground.setBackground(new ColorDrawable(0x00000000)); // 双重保障(兼容低版本) + ivBackground.setBackground(new ColorDrawable(0x00000000)); // 低版本兼容 - this.addView(ivBackground); + this.addView(ivBackground); // 添加到父容器(控件本身) } -// private void initBackgroundImagePath() { -// if (isPreviewMode) { -// backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getPreviewBackgroundFilePath(); -// } else { -// backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getCurrentBackgroundFilePath(); -// } -// } - /** - * 拷贝图片文件到背景资源目录(正式背景) - */ -// public void setImageViewSource(String srcBackgroundPath) { -// initBackgroundImagePath(); -// if (backgroundSourceFilePath == null) { -// 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, "预览图片路径为空"); - setDefaultImageViewBackground(); - return; - } - - File previewFile = new File(previewImagePath); - if (!previewFile.exists() || !previewFile.isFile()) { - LogUtils.e(TAG, "预览图片不存在或不是文件:" + previewImagePath); - setDefaultImageViewBackground(); - return; - } - - // 计算图片原始宽高比 - if (!calculateImageAspectRatio(previewFile)) { - LogUtils.e(TAG, "预览图片尺寸无效,无法预览"); - setDefaultImageViewBackground(); - return; - } - - // 压缩加载预览图片(保持比例) - Bitmap previewBitmap = decodeBitmapWithCompress(previewFile, 1080, 1920); - if (previewBitmap == null) { - LogUtils.e(TAG, "预览图片加载失败"); - setDefaultImageViewBackground(); - 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 + ",宽高比:" + imageAspectRatio); - } - */ - - /** - * 退出预览模式,恢复显示正式背景图片 - */ - /*public void exitPreviewMode() { - if (isPreviewMode) { - loadAndSetImageViewBackground(); - isPreviewMode = false; - LogUtils.d(TAG, "退出预览模式,恢复正式背景"); - } - }*/ - - /** - * 公共函数:供外部类调用,重新加载正式背景图片(刷新显示) + * 【对外提供】重新加载正式背景图片(从正式Bean获取路径) + * 用于:退出预览模式、恢复默认背景、正式背景更新后刷新 */ public void reloadCurrentBackground() { - LogUtils.d(TAG, "外部调用重新加载背景图片"); - backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getCurrentBackgroundFilePath(); - loadAndSetImageViewBackground(); + LogUtils.d(TAG, "=== 开始重新加载正式背景 ==="); + isPreviewMode = false; // 标记为正式模式 + // 从工具类获取最新正式背景路径(确保路径同步) + String backgroundPath = backgroundSourceUtils.getCurrentBackgroundFilePath(); + // 加载并显示图片 + loadAndSetImageViewBackground(backgroundPath); } - + + /** + * 【对外提供】重新加载预览背景图片(从预览Bean获取路径) + * 修复:增加isUseBackgroundFile校验,确保启用背景图时才加载 + */ public void reloadPreviewBackground() { - LogUtils.d(TAG, "外部调用重新加载背景图片"); - backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getPreviewBackgroundFilePath(); - loadAndSetImageViewBackground(); + LogUtils.d(TAG, "=== 开始重新加载预览背景 ==="); + isPreviewMode = true; // 标记为预览模式 + // 从工具类获取最新预览背景Bean + BackgroundBean previewBean = backgroundSourceUtils.getPreviewBackgroundBean(); + // 修复:若未启用背景图,直接显示透明背景(避免无效加载) + if (!previewBean.isUseBackgroundFile()) { + LogUtils.d(TAG, "预览Bean未启用背景图(isUseBackgroundFile=false),显示透明背景"); + setDefaultTransparentBackground(); + return; + } + // 优先加载压缩图,无则加载原图(保持原逻辑) + String backgroundPath; + if (previewBean.isUseScaledCompress()) { + backgroundPath = backgroundSourceUtils.getPreviewBackgroundScaledCompressFilePath(); + } else { + backgroundPath = backgroundSourceUtils.getPreviewBackgroundFilePath(); + } + // 加载并显示图片 + loadAndSetImageViewBackground(backgroundPath); + } + + /** + * 【对外提供】预览指定路径的临时图片(直接传入路径,不依赖Bean) + * 用于:临时预览本地图片、测试图片等场景 + * @param previewImagePath 临时图片绝对路径(空则显示透明) + */ + public void previewBackgroundImage(String previewImagePath) { + LogUtils.d(TAG, "=== 开始预览指定路径图片 ==="); + isPreviewMode = true; // 标记为预览模式 + // 加载并显示指定路径图片 + loadAndSetImageViewBackground(previewImagePath); } /** - * 加载正式背景图片并设置到 ImageView(全透明背景 + 不拉伸居中平铺) + * 【核心逻辑】加载图片并设置到ImageView(统一处理,避免重复代码) + * @param imagePath 图片绝对路径(可为空) */ - private void loadAndSetImageViewBackground() { - if (backgroundSourceFilePath == null) { - LogUtils.d(TAG, "backgroundSourceFilePath == null"); - setDefaultImageViewBackground(); + private void loadAndSetImageViewBackground(String imagePath) { + // 1. 路径校验(空路径/无效路径 → 显示透明背景) + if (imagePath == null || imagePath.isEmpty()) { + LogUtils.e(TAG, "图片路径为空,显示透明背景"); + setDefaultTransparentBackground(); return; } - //ToastUtils.show(String.format("backgroundSourceFilePath : %s", backgroundSourceFilePath)); - File backgroundFile = new File(backgroundSourceFilePath); + // 2. 文件校验(文件不存在/不是文件 → 显示透明背景) + File backgroundFile = new File(imagePath); if (!backgroundFile.exists() || !backgroundFile.isFile()) { - LogUtils.e(TAG, "背景图片不存在:" + backgroundSourceFilePath); - setDefaultImageViewBackground(); + LogUtils.e(TAG, "图片文件不存在或无效:" + imagePath); + setDefaultTransparentBackground(); return; } - // 计算图片原始宽高比 + // 3. 计算图片原始宽高比(确保不拉伸) if (!calculateImageAspectRatio(backgroundFile)) { - setDefaultImageViewBackground(); + LogUtils.e(TAG, "图片尺寸无效,无法加载"); + setDefaultTransparentBackground(); return; } - // 压缩加载 Bitmap(保持比例) + // 4. 压缩加载Bitmap(避免OOM,保持原始比例) Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); if (bitmap == null) { - LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap"); - setDefaultImageViewBackground(); + LogUtils.e(TAG, "图片压缩加载失败:" + imagePath); + setDefaultTransparentBackground(); return; } - // 设置图片(保持ImageView透明背景) + // 5. 设置图片到ImageView(保持透明背景) Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { ivBackground.setBackground(backgroundDrawable); @@ -285,43 +191,51 @@ public class BackgroundView extends RelativeLayout { ivBackground.setBackgroundDrawable(backgroundDrawable); } - // 调整ImageView尺寸(居中平铺,不拉伸) + // 6. 调整ImageView尺寸(居中平铺,适配控件大小) adjustImageViewSize(); - LogUtils.d(TAG, "ImageView 背景加载成功(全透明+不拉伸),宽高比:" + imageAspectRatio); + + LogUtils.d(TAG, "图片加载成功(" + (isPreviewMode ? "预览模式" : "正式模式") + ")"); + LogUtils.d(TAG, "图片路径:" + imagePath + ",宽高比:" + imageAspectRatio); } /** - * 计算图片原始宽高比(宽/高) + * 计算图片原始宽高比(宽/高)→ 控制不拉伸的核心 + * @param file 图片文件(非空) + * @return 成功:true,失败:false */ private boolean calculateImageAspectRatio(File file) { try { BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; + options.inJustDecodeBounds = true; // 仅获取尺寸,不加载图片(省内存) BitmapFactory.decodeFile(file.getAbsolutePath(), options); int imageWidth = options.outWidth; int imageHeight = options.outHeight; + // 校验尺寸有效性(宽/高必须大于0) 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()); + LogUtils.e(TAG, "计算图片宽高比失败:" + e.getMessage(), e); return false; } } /** - * 调整ImageView尺寸(不拉伸,居中平铺) + * 调整ImageView尺寸(核心:不拉伸,居中平铺,适配控件大小) + * 效果:ImageView按图片比例缩放,最大尺寸不超过控件,同时在控件中居中 */ private void adjustImageViewSize() { - int parentWidth = getWidth(); // 控件宽度(已填充父视图) - int parentHeight = getHeight(); // 控件高度(已填充父视图) + int parentWidth = getWidth(); // 控件宽度(已完全填充父视图) + int parentHeight = getHeight(); // 控件高度(已完全填充父视图) + // 若父容器未测量完成(宽度/高度为0),延迟调整(避免尺寸计算错误) if (parentWidth == 0 || parentHeight == 0) { post(new Runnable() { @Override @@ -329,72 +243,86 @@ public class BackgroundView extends RelativeLayout { adjustImageViewSize(); } }); + LogUtils.d(TAG, "父容器未测量完成,延迟调整ImageView尺寸"); return; } int imageViewWidth, imageViewHeight; - if (imageAspectRatio >= 1.0f) { // 横图 + // 按图片原始比例,计算ImageView最大可行尺寸(不超过控件,不拉伸) + if (imageAspectRatio >= 1.0f) { // 横图(宽 ≥ 高):优先适配控件宽度 imageViewWidth = Math.min(parentWidth, (int) (parentHeight * imageAspectRatio)); imageViewHeight = (int) (imageViewWidth / imageAspectRatio); - } else { // 竖图 + } else { // 竖图(宽 < 高):优先适配控件高度 imageViewHeight = Math.min(parentHeight, (int) (parentWidth / imageAspectRatio)); imageViewWidth = (int) (imageViewHeight * imageAspectRatio); } - // 应用尺寸 + // 应用尺寸到ImageView(更新布局参数) RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivBackground.getLayoutParams(); layoutParams.width = imageViewWidth; layoutParams.height = imageViewHeight; ivBackground.setLayoutParams(layoutParams); - LogUtils.d(TAG, "ImageView 尺寸调整完成:宽=" + imageViewWidth + ", 高=" + imageViewHeight); + LogUtils.d(TAG, "ImageView尺寸调整完成:宽=" + imageViewWidth + ", 高=" + imageViewHeight + "(控件尺寸:" + parentWidth + "x" + parentHeight + ")"); } /** - * 带压缩的 Bitmap 解码(保持比例,避免 OOM) + * 带压缩的Bitmap解码(仅压缩大小,不改变原始比例,避免OOM) + * @param file 图片文件 + * @param maxWidth 最大宽度(1080px,适配主流手机屏幕) + * @param maxHeight 最大高度(1920px,适配主流手机屏幕) + * @return 压缩后的Bitmap(null表示失败) */ 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; + inSampleSize = 1; // 最小压缩比例为1(不压缩) } + // 正式解码图片(压缩+省内存配置) options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; - options.inPreferredConfig = Bitmap.Config.RGB_565; + options.inPreferredConfig = Bitmap.Config.RGB_565; // 比ARGB_8888省一半内存 return BitmapFactory.decodeFile(file.getAbsolutePath(), options); } catch (Exception e) { - LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage()); + LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage(), e); return null; } } /** - * 设置默认背景(全透明兜底,避免空白) + * 设置默认透明背景(图片加载失败/路径无效时兜底) + * 确保:无图片时控件完全透明,不遮挡下层视图 */ - private void setDefaultImageViewBackground() { - // 关键:默认背景设为全透明(而非默认图,避免遮挡下层视图) + private void setDefaultTransparentBackground() { ivBackground.setBackground(new ColorDrawable(0x00000000)); // 全透明背景 - imageAspectRatio = 1.0f; - adjustImageViewSize(); + imageAspectRatio = 1.0f; // 重置宽高比(避免影响下次加载) + adjustImageViewSize(); // 调整尺寸(确保ImageView居中且不占位异常) LogUtils.d(TAG, "已设置默认透明背景"); } + /** + * 父容器尺寸变化时(如屏幕旋转),重新调整ImageView尺寸 + * 确保:控件尺寸变化后,图片仍保持不拉伸+居中平铺 + */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - adjustImageViewSize(); + LogUtils.d(TAG, "控件尺寸变化:旧尺寸=" + oldw + "x" + oldh + ",新尺寸=" + w + "x" + h); + adjustImageViewSize(); // 重新调整ImageView尺寸 } /** - * 对外提供:判断当前是否处于预览模式 + * 【对外提供】判断当前是否处于预览模式 + * 用于:Activity中判断是否需要提交预览背景 */ public boolean isPreviewMode() { return isPreviewMode;