20251201_004105_116

This commit is contained in:
2025-12-01 00:41:10 +08:00
parent 5a9469317d
commit e408b5cbde
5 changed files with 787 additions and 698 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #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 stageCount=13
libraryProject= libraryProject=
baseVersion=15.11 baseVersion=15.11
publishVersion=15.11.12 publishVersion=15.11.12
buildCount=0 buildCount=4
baseBetaVersion=15.11.13 baseBetaVersion=15.11.13

View File

@@ -52,46 +52,42 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private File mfBackgroundDir; // 背景图片存储文件夹 private File mfBackgroundDir; // 背景图片存储文件夹
private File mfPictureDir; // 拍照与剪裁临时文件夹 private File mfPictureDir; // 拍照与剪裁临时文件夹
private File mfTakePhoto; // 拍照文件 private File mfTakePhoto; // 拍照文件
//private File mfRecivedPicture; // 接收的图片文件
// 背景视图预览图片的文件名 // 背景视图预览图片的文件名
private String preViewFilePath = ""; private String preViewFilePath = "";
private String preViewFileUrl = ""; private String preViewFileUrl = "";
BackgroundView bvPreviewBackground; BackgroundView bvPreviewBackground;
boolean isCommitSettings = false; boolean isCommitSettings = false;
// 静态变量 // 静态变量
// 源文件的临时剪裁图片保存名称 // 源文件的临时剪裁图片保存名称
private static String _mSourceCropTempFileName = "SourceCropTemp.jpg"; private static String _mSourceCropTempFileName = "SourceCropTemp.jpg";
// 源文件的临时剪裁图片保存文件对象 // 源文件的临时剪裁图片保存文件对象
private static File _mSourceCropTempFile; private static File _mSourceCropTempFile;
// 源文件的剪裁图片保存名称 // 源文件的剪裁图片保存名称
private static String _mSourceCroppedFileName = "SourceCropped.jpg"; private static String _mSourceCroppedFileName = "SourceCropped.jpg";
// 源文件的剪裁图片保存文件对象 // 源文件的剪裁图片保存文件对象
private static File _mSourceCroppedFile; private static File _mSourceCroppedFile;
// 源文件的剪裁图片保存路径 // 源文件的剪裁图片保存路径
private static String _mSourceCroppedFilePath; private static String _mSourceCroppedFilePath;
private static String _mszCommonFileType = "jpeg"; private static String _mszCommonFileType = "jpeg";
private int mnPictureCompress = 100; private int mnPictureCompress = 100;
//private static String _RecivedBackgroundFileName;
@Override @Override
public Activity getActivity() { public Activity getActivity() {
return this; return this;
} }
@Override @Override
public String getTag() { public String getTag() {
return TAG; return TAG;
} }
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_backgroundpicture); setContentView(R.layout.activity_backgroundpicture);
bvPreviewBackground = (BackgroundView) findViewById(R.id.activitybackgroundpictureBackgroundView1); bvPreviewBackground = (BackgroundView) findViewById(R.id.activitybackgroundpictureBackgroundView1);
//initEnv();
// 初始化工具类和文件夹 // 初始化工具类和文件夹
mBackgroundSourceUtils = BackgroundSourceUtils.getInstance(this); mBackgroundSourceUtils = BackgroundSourceUtils.getInstance(this);
@@ -107,13 +103,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
// 初始化文件对象 // 初始化文件对象
mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg"); mfTakePhoto = new File(mfPictureDir, "TakePhoto.jpg");
//mfTempCropPicture = new File(mfPictureDir, "TempCrop.jpg");
//mfRecivedPicture = getRecivedPictureFile();
_mSourceCropTempFile = new File(mfBackgroundDir, _mSourceCropTempFileName); _mSourceCropTempFile = new File(mfBackgroundDir, _mSourceCropTempFileName);
_mSourceCroppedFile = new File(mfBackgroundDir, _mSourceCroppedFileName); _mSourceCroppedFile = new File(mfBackgroundDir, _mSourceCroppedFileName);
_mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString(); _mSourceCroppedFilePath = _mSourceCroppedFile.getAbsolutePath().toString();
LogUtils.d(TAG, String.format("_mSourceCroppedFilePath : %s", _mSourceCroppedFilePath)); LogUtils.d(TAG, String.format("_mSourceCroppedFilePath : %s", _mSourceCroppedFilePath));
// 初始化工具栏 // 初始化工具栏
mAToolbar = (AToolbar) findViewById(R.id.toolbar); mAToolbar = (AToolbar) findViewById(R.id.toolbar);
@@ -121,11 +114,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture); mAToolbar.setSubtitle(R.string.subtitle_activity_backgroundpicture);
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
mAToolbar.setNavigationOnClickListener(new View.OnClickListener() { mAToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
finish(); // 点击导航栏返回按钮,触发 finish() finish(); // 点击导航栏返回按钮,触发 finish()
} }
}); });
// 设置按钮点击事件 // 设置按钮点击事件
findViewById(R.id.activitybackgroundpictureAButton5).setOnClickListener(onOriginNullClickListener); 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.activitybackgroundpictureAButton2).setOnClickListener(onSelectPictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener); findViewById(R.id.activitybackgroundpictureAButton3).setOnClickListener(onCropPictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener); findViewById(R.id.activitybackgroundpictureAButton6).setOnClickListener(onCropFreePictureClickListener);
findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener); findViewById(R.id.activitybackgroundpictureAButton7).setOnClickListener(onPixelPickerClickListener);
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener); findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
// 初始预览:加载当前背景到预览并刷新视图
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
utils.setCurrentSourceToPreview(); utils.setCurrentSourceToPreview();
bvPreviewBackground.reloadPreviewBackground(); bvPreviewBackground.reloadPreviewBackground(); // 修复:调用预览刷新方法
// 处理分享的图片 // 处理分享的图片
Intent intent = getIntent(); 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() { public static String getBackgroundFileName() {
return _mSourceCroppedFileName; return _mSourceCroppedFileName;
} }
@Override @Override
public void onAcceptRecivedPicture(String szPreRecivedPictureName) { public void onAcceptRecivedPicture(String szPreRecivedPictureName) {
ToastUtils.show("onAcceptRecivedPicture not yet."); 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("图片复制失败,请重试");
// }
} }
/** /**
* 更新背景图片预览 * 更新背景图片预览
* 如果sourceFile参数为空则加载旧的背景图片资源 * 如果sourceFile参数为空则加载旧的背景图片资源
*/ */
public void updateBackgroundView(File sourceFile, String sourceFileInfo) { public void updateBackgroundView(File sourceFile, String sourceFileInfo) {
LogUtils.d(TAG, "updatePreviewBackground"); LogUtils.d(TAG, "updatePreviewBackground");
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
if (sourceFile == null) { if (sourceFile == null) {
bvPreviewBackground.reloadCurrentBackground(); bvPreviewBackground.reloadCurrentBackground(); // 修复:调用正式背景刷新
} else { } else {
utils.saveFileToPreviewBean(sourceFile, sourceFileInfo); utils.saveFileToPreviewBean(sourceFile, sourceFileInfo);
bvPreviewBackground.reloadPreviewBackground(); 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);
//// }
// }
} }
// 点击事件监听器 // 点击事件监听器
@@ -230,10 +178,11 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
BackgroundBean bean = utils.getCurrentBackgroundBean(); BackgroundBean bean = utils.getCurrentBackgroundBean();
bean.setIsUseBackgroundFile(false); bean.setIsUseBackgroundFile(false);
utils.saveSettings(); utils.saveSettings();
bvPreviewBackground.reloadPreviewBackground(); bvPreviewBackground.reloadPreviewBackground(); // 修复:刷新预览
} }
}; };
// 修复:选择图片后添加视图刷新逻辑
private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() { private View.OnClickListener onSelectPictureClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@@ -295,35 +244,29 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() { private View.OnClickListener onReceivedPictureClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
ToastUtils.show("onReceivedPictureClickListener not yet."); ToastUtils.show("onReceivedPictureClickListener not yet.");
// BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
// utils.getCurrentBackgroundBean().setIsUseBackgroundFile(true);
// utils.saveSettings();
// updateBackgroundView();
} }
}; };
private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() { private View.OnClickListener onPixelPickerClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
// 从文件路径启动像素拾取活动 // 从文件路径启动像素拾取活动
//String imagePath = "/storage/emulated/0/DCIM/Camera/sample.jpg"; String imagePath = _mSourceCroppedFile.toString();
String imagePath = _mSourceCroppedFile.toString(); Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class);
Intent intent = new Intent(getApplicationContext(), PixelPickerActivity.class); intent.putExtra("imagePath", imagePath);
intent.putExtra("imagePath", imagePath); startActivity(intent);
startActivity(intent);
//App.getWinBoLLActivityManager().startWinBoLLActivity(getActivity(), intent, PixelPickerActivity.class);
} }
}; };
private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() { private View.OnClickListener onCleanPixelClickListener = new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
BackgroundBean bean = utils.getCurrentBackgroundBean(); BackgroundBean bean = utils.getCurrentBackgroundBean();
bean.setPixelColor(0); bean.setPixelColor(0);
utils.saveSettings(); utils.saveSettings();
setBackgroundColor(); setBackgroundColor();
} }
}; };
@@ -333,7 +276,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
void compressQualityToRecivedPicture(Bitmap bitmap) { void compressQualityToRecivedPicture(Bitmap bitmap) {
OutputStream outStream = null; OutputStream outStream = null;
try { try {
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
File fRecivedPicture = new File(utils.getPreviewBackgroundScaledCompressFilePath()); File fRecivedPicture = new File(utils.getPreviewBackgroundScaledCompressFilePath());
if (!fRecivedPicture.exists()) { if (!fRecivedPicture.exists()) {
fRecivedPicture.createNewFile(); fRecivedPicture.createNewFile();
@@ -361,18 +304,18 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
} }
/** /**
* 启动图片裁剪活动 * 启动图片裁剪活动核心修复传递BackgroundView实际宽高比例替代Bean默认值
* @param isCropFree 是否自由裁剪 * @param isCropFree 是否自由裁剪
*/ */
public void startCropImageActivity(boolean isCropFree) { public void startCropImageActivity(boolean isCropFree) {
LogUtils.d(TAG, "startCropImageActivity"); LogUtils.d(TAG, "startCropImageActivity");
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getPreviewBackgroundBean(); BackgroundBean bean = utils.getPreviewBackgroundBean();
bean.setIsUseScaledCompress(true); bean.setIsUseScaledCompress(true);
utils.saveSettings(); utils.saveSettings();
File fRecivedPicture = new File(utils.getPreviewBackgroundFilePath()); File fRecivedPicture = new File(utils.getPreviewBackgroundFilePath());
Uri uri = UriUtil.getUriForFile(this, fRecivedPicture); Uri uri = UriUtil.getUriForFile(this, fRecivedPicture);
LogUtils.d(TAG, "uri : " + uri.toString()); LogUtils.d(TAG, "uri : " + uri.toString());
@@ -396,9 +339,28 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
intent.putExtra("crop", "true"); intent.putExtra("crop", "true");
intent.putExtra("noFaceDetection", true); intent.putExtra("noFaceDetection", true);
// 修复核心非自由裁剪时传递bvPreviewBackground控件的实际宽高比例确保裁剪与控件匹配
if (!isCropFree) { if (!isCropFree) {
intent.putExtra("aspectX", bean.getBackgroundWidth()); // 1. 优先获取BackgroundView的实际宽高控件已填充父视图宽高=父容器尺寸)
intent.putExtra("aspectY", bean.getBackgroundHeight()); 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); 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) { private int calculateGCD(int a, int b) {
if (bitmap == null) { if (b == 0) {
ToastUtils.show("剪裁图片为空"); return a;
return;
} }
return calculateGCD(b, a % b);
}
// 内存优化:大图片自动缩放 /**
Bitmap scaledBitmap = bitmap; * 保存剪裁后的Bitmap优化版修复裁剪后加载不到图片问题
if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB */
float scale = 1.0f; private void saveCropBitmap(Bitmap bitmap) {
while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) { if (bitmap == null) {
scale -= 0.2f; // 每次缩小20% ToastUtils.show("剪裁图片为空");
if (scale < 0.2f) break; // 最小缩放到20% // 修复:临时文件异常时也清理
scaledBitmap = scaleBitmap(scaledBitmap, scale); if (_mSourceCropTempFile.exists()) {
} _mSourceCropTempFile.delete();
if (scaledBitmap != bitmap) { LogUtils.d(TAG, "裁剪图片为空,清理临时文件:" + _mSourceCropTempFile.getPath());
bitmap.recycle(); // 回收原Bitmap }
} return;
} }
// 优化:创建保存目录 // 内存优化:大图片自动缩放(保持原逻辑)
File backgroundDir = new File(mBackgroundSourceUtils.getBackgroundSourceDirPath()); Bitmap scaledBitmap = bitmap;
if (!backgroundDir.exists()) { if (bitmap.getByteCount() > 10 * 1024 * 1024) { // 超过10MB
if (!backgroundDir.mkdirs()) { float scale = 1.0f;
ToastUtils.show("无法创建保存目录"); while (scaledBitmap.getByteCount() > 5 * 1024 * 1024) {
if (scaledBitmap != bitmap) scaledBitmap.recycle(); scale -= 0.2f; // 每次缩小20%
return; 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()) { BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
if (!fScaledCompressBitmapFile.delete()) { String scaledCompressFileName = utils.getPreviewBackgroundScaledCompressFilePath();
ToastUtils.show("无法删除旧文件"); File fScaledCompressBitmapFile = new File(scaledCompressFileName);
if (scaledBitmap != bitmap) scaledBitmap.recycle();
return;
}
}
FileOutputStream fos = null; // 优化:检查文件是否可写(保持原逻辑)
try { if (fScaledCompressBitmapFile.exists() && !fScaledCompressBitmapFile.canWrite()) {
fos = new FileOutputStream(fScaledCompressBitmapFile); if (!fScaledCompressBitmapFile.delete()) {
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos); ToastUtils.show("无法删除旧文件");
fos.flush(); if (scaledBitmap != bitmap) scaledBitmap.recycle();
if (success) { return;
}
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(fScaledCompressBitmapFile);
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
fos.flush();
if (success) {
ToastUtils.show("图片压缩保存成功"); ToastUtils.show("图片压缩保存成功");
BackgroundSourceUtils.getInstance(this).getPreviewBackgroundBean().setIsUseScaledCompress(true); BackgroundBean previewBean = utils.getPreviewBackgroundBean();
BackgroundSourceUtils.getInstance(this).saveSettings(); // 修复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(); bvPreviewBackground.reloadPreviewBackground();
} else { } else {
ToastUtils.show("图片压缩保存失败"); ToastUtils.show("图片压缩保存失败");
BackgroundSourceUtils.getInstance(this).getPreviewBackgroundBean().setIsUseScaledCompress(false); BackgroundBean previewBean = utils.getPreviewBackgroundBean();
BackgroundSourceUtils.getInstance(this).saveSettings(); previewBean.setIsUseScaledCompress(false);
utils.saveSettings();
// 修复:保存失败时清理临时文件
if (_mSourceCropTempFile.exists()) {
_mSourceCropTempFile.delete();
LogUtils.d(TAG, "裁剪失败,清理临时文件:" + _mSourceCropTempFile.getPath());
}
// 修复:保存失败时刷新原始预览图
bvPreviewBackground.reloadPreviewBackground(); bvPreviewBackground.reloadPreviewBackground();
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
LogUtils.e(TAG, "文件未找到" + e); LogUtils.e(TAG, "文件未找到" + e);
ToastUtils.show("文件未找到" + e); ToastUtils.show("文件未找到" + e.getMessage());
} catch (IOException e) { // 异常时清理临时文件
LogUtils.e(TAG, "写入异常" + e); if (_mSourceCropTempFile.exists()) {
ToastUtils.show("写入异常" + e); _mSourceCropTempFile.delete();
} finally { }
if (fos != null) { } catch (IOException e) {
try { LogUtils.e(TAG, "写入异常" + e);
fos.close(); ToastUtils.show("写入异常:" + e.getMessage());
} catch (IOException e) { // 异常时清理临时文件
LogUtils.e(TAG, "流关闭异常" + e); if (_mSourceCropTempFile.exists()) {
ToastUtils.show("流关闭异常" + e); _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()) { if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
scaledBitmap.recycle(); scaledBitmap.recycle(); // 回收缩放后的Bitmap避免内存泄漏
} }
} }
} }
@@ -509,8 +521,8 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* 分享图片 * 分享图片
*/ */
void sharePicture() { void sharePicture() {
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this); BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(this);
File fRecivedPicture = new File(utils.getCurrentBackgroundFilePath()); File fRecivedPicture = new File(utils.getCurrentBackgroundFilePath());
Uri uri = UriUtil.getUriForFile(this, fRecivedPicture); Uri uri = UriUtil.getUriForFile(this, fRecivedPicture);
Intent shareIntent = new Intent(Intent.ACTION_SEND); Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, uri); shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
@@ -519,95 +531,100 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
startActivity(Intent.createChooser(shareIntent, "Share Image")); startActivity(Intent.createChooser(shareIntent, "Share Image"));
} }
// public File getRecivedPictureFile() { @Override
// BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this); protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// utils.loadSettings(); super.onActivityResult(requestCode, resultCode, data);
// return new File(utils.getBackgroundSourceDirPath(), _RecivedBackgroundFileName); 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) { // 修复保存图片到预览Bean后立即刷新BackgroundView显示预览图
// 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);
utils.saveFileToPreviewBean(fSrcImage, selectedImage.toString()); utils.saveFileToPreviewBean(fSrcImage, selectedImage.toString());
startCropImageActivity(false); bvPreviewBackground.reloadPreviewBackground();
//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;
}
if (cropBitmap != null) { // 启动裁剪(保持原逻辑,裁剪比例已修复)
saveCropBitmap(cropBitmap); startCropImageActivity(false);
} else { } catch (Exception e) {
ToastUtils.show("获取剪裁图片失败"); LogUtils.e(TAG, "选择图片异常" + e);
} ToastUtils.show("选择图片失败:" + e.getMessage());
} catch (OutOfMemoryError e) { // 异常时清理临时文件(避免残留)
LogUtils.e(TAG, "内存溢出" + e); if (_mSourceCropTempFile.exists()) {
ToastUtils.show("保存失败:内存不足,请尝试裁剪更小的图片"); _mSourceCropTempFile.delete();
} catch (Exception e) { LogUtils.d(TAG, "选择图片异常,清理临时文件:" + _mSourceCropTempFile.getPath());
LogUtils.e(TAG, "剪裁保存异常" + e); }
ToastUtils.show("保存失败:" + e.getMessage()); }
}/* finally { } else if (requestCode == REQUEST_TAKE_PHOTO && resultCode == RESULT_OK) {
// 安全删除临时文件 LogUtils.d(TAG, "REQUEST_TAKE_PHOTO");
if (mfTempCropPicture.exists()) { Bundle extras = data.getExtras();
mfTempCropPicture.delete(); if (extras != null) {
} Bitmap imageBitmap = (Bitmap) extras.get("data");
}*/ if (imageBitmap != null) {
} else if (resultCode != RESULT_OK) { compressQualityToRecivedPicture(imageBitmap);
LogUtils.d(TAG, "操作取消或失败requestCode: " + requestCode); // 修复:拍照压缩后,刷新预览图
ToastUtils.show("操作已取消"); 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() { void setBackgroundColor() {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
BackgroundBean bean = utils.getCurrentBackgroundBean(); BackgroundBean bean = utils.getCurrentBackgroundBean();
int nPixelColor = bean.getPixelColor(); int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1); RelativeLayout mainLayout = findViewById(R.id.activitybackgroundpictureRelativeLayout1);
mainLayout.setBackgroundColor(nPixelColor); mainLayout.setBackgroundColor(nPixelColor);
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
setBackgroundColor(); setBackgroundColor();
} }
public void onNetworkBackgroundDialog(View view) { public void onNetworkBackgroundDialog(View view) {
// 在需要显示对话框的地方(如网络状态监听回调中) // 在需要显示对话框的地方(如网络状态监听回调中)
NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() { NetworkBackgroundDialog dialog = new NetworkBackgroundDialog(this, new NetworkBackgroundDialog.OnDialogClickListener() {
@Override @Override
public void onConfirm(String szConfirmFilePath, String szConfirmFileUrl) { public void onConfirm(String szConfirmFilePath, String szConfirmFileUrl) {
//ToastUtils.show("onConfirm");
// 保存预览资源信息 // 保存预览资源信息
preViewFilePath = szConfirmFilePath; preViewFilePath = szConfirmFilePath;
preViewFileUrl = szConfirmFileUrl; preViewFileUrl = szConfirmFileUrl;
@@ -673,31 +689,33 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override @Override
public void onCancel() { public void onCancel() {
//ToastUtils.show("onCancel"); // 取消逻辑
} }
}); });
// 可选:修改对话框标题和内容(适配自定义场景) // 可选:修改对话框标题和内容(适配自定义场景)
dialog.setTitle("网络图片下载对话框"); dialog.setTitle("网络图片下载对话框");
dialog.setContent("是否下载地址中的图片资源,作为应用背景图片?"); dialog.setContent("是否下载地址中的图片资源,作为应用背景图片?");
// 显示对话框 // 显示对话框
dialog.show(); dialog.show();
} }
interface OnRecivedPictureListener { interface OnRecivedPictureListener {
void onRecivedPicture(String srcFilePath, String srcFileUrl); void onRecivedPicture(String srcFilePath, String srcFileUrl);
} }
OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){ OnRecivedPictureListener onRecivedPictureListener = new OnRecivedPictureListener(){
@Override @Override
public void onRecivedPicture(String srcFilePath, String srcFileUrl) { public void onRecivedPicture(String srcFilePath, String srcFileUrl) {
BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this); BackgroundSourceUtils utils= BackgroundSourceUtils.getInstance(BackgroundSettingsActivity.this);
utils.saveFileToPreviewBean(new File(srcFilePath), srcFileUrl); utils.saveFileToPreviewBean(new File(srcFilePath), srcFileUrl);
startCropImageActivity(true); // 修复:网络图片下载后刷新预览
} bvPreviewBackground.reloadPreviewBackground();
}; startCropImageActivity(true);
}
};
/** /**
* 重写finish方法确保所有退出场景都触发Toast * 重写finish方法确保所有退出场景都触发Toast
@@ -705,7 +723,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override @Override
public void finish() { public void finish() {
if (!isCommitSettings) { if (!isCommitSettings) {
YesNoAlertDialog.show(this, "应用背景更改提示:", "是否应用预览图片?", new YesNoAlertDialog.OnDialogResultListener(){ YesNoAlertDialog.show(this, "应用背景更改提示:", "是否应用预览图片?", new YesNoAlertDialog.OnDialogResultListener(){
@Override @Override
public void onNo() { public void onNo() {
@@ -725,9 +743,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
finish(); finish();
} }
}); });
} else { } else {
super.finish(); super.finish();
} }
} }
} }

View File

@@ -1,143 +1,172 @@
package cc.winboll.studio.powerbell.model; package cc.winboll.studio.powerbell.model;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 11:52:28
* @Describe 应用背景图片数据类
*/
import android.util.JsonReader; import android.util.JsonReader;
import android.util.JsonWriter; import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean; import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException; import java.io.IOException;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 11:52:28
* @Describe 应用背景图片数据类(存储正式/预览背景配置支持JSON序列化/反序列化)
*/
public class BackgroundBean extends BaseBean { public class BackgroundBean extends BaseBean {
public static final String TAG = "BackgroundPictureBean"; public static final String TAG = "BackgroundPictureBean";
String backgroundFileName = ""; // 核心字段背景图片文件名对应BackgroundSource目录下的图片文件
String backgroundFileInfo = ""; private String backgroundFileName = "";
boolean isUseBackgroundFile = false; // 附加字段图片信息如Uri、网络地址等仅作备注不参与路径生成
String backgroundScaledCompressFileName = ""; private String backgroundFileInfo = "";
boolean isUseScaledCompress = false; // 控制字段是否启用背景图片true-显示背景图false-显示透明背景)
int backgroundWidth = 100; private boolean isUseBackgroundFile = false;
int backgroundHeight = 100; // 核心字段压缩后背景图片文件名对应BackgroundSource目录下的压缩图片
// 图片拾取像素颜色 private String backgroundScaledCompressFileName = "";
int pixelColor = 0; // 控制字段是否启用压缩背景图true-加载压缩图false-加载原图)
private boolean isUseScaledCompress = false;
// 裁剪比例字段背景图宽高比默认1:1用于固定比例裁剪
private int backgroundWidth = 100;
private int backgroundHeight = 100;
// 像素拾取字段:拾取的像素颜色(用于纯色背景)
private int pixelColor = 0;
/**
* 无参构造器必须JSON反序列化时需默认构造器
*/
public BackgroundBean() { public BackgroundBean() {
} }
public void setBackgroundScaledCompressFileName(String backgroundScaledCompressFileName) { // ====================================== Getter/Setter 方法(全字段,确保序列化/反序列化完整)======================================
this.backgroundScaledCompressFileName = backgroundScaledCompressFileName; public String getBackgroundFileName() {
} return backgroundFileName;
}
public String getBackgroundScaledCompressFileName() { public void setBackgroundFileName(String backgroundFileName) {
return backgroundScaledCompressFileName; this.backgroundFileName = backgroundFileName == null ? "" : backgroundFileName; // 防null避免空指针
} }
public void setIsUseScaledCompress(boolean isUseScaledCompress) { public String getBackgroundFileInfo() {
this.isUseScaledCompress = isUseScaledCompress; return backgroundFileInfo;
} }
public boolean isUseScaledCompress() { public void setBackgroundFileInfo(String backgroundFileInfo) {
return isUseScaledCompress; this.backgroundFileInfo = backgroundFileInfo == null ? "" : backgroundFileInfo; // 防null避免空指针
} }
public void setIsUseBackgroundFile(boolean isUseBackgroundFile) { public boolean isUseBackgroundFile() {
this.isUseBackgroundFile = isUseBackgroundFile; return isUseBackgroundFile;
} }
public boolean isUseBackgroundFile() { public void setIsUseBackgroundFile(boolean isUseBackgroundFile) {
return isUseBackgroundFile; this.isUseBackgroundFile = isUseBackgroundFile;
} }
public void setBackgroundFileInfo(String backgroundFileInfo) { public String getBackgroundScaledCompressFileName() {
this.backgroundFileInfo = backgroundFileInfo; return backgroundScaledCompressFileName;
} }
public String getBackgroundFileInfo() { public void setBackgroundScaledCompressFileName(String backgroundScaledCompressFileName) {
return backgroundFileInfo; this.backgroundScaledCompressFileName = backgroundScaledCompressFileName == null ? "" : backgroundScaledCompressFileName; // 防null
} }
public void setBackgroundFileName(String backgroundFileName) { public boolean isUseScaledCompress() {
this.backgroundFileName = backgroundFileName; return isUseScaledCompress;
} }
public String getBackgroundFileName() { public void setIsUseScaledCompress(boolean isUseScaledCompress) {
return backgroundFileName; this.isUseScaledCompress = isUseScaledCompress;
}
public void setPixelColor(int pixelColor) {
this.pixelColor = pixelColor;
}
public int getPixelColor() {
return pixelColor;
}
public void setBackgroundWidth(int backgroundWidth) {
this.backgroundWidth = backgroundWidth;
} }
public int getBackgroundWidth() { public int getBackgroundWidth() {
return backgroundWidth; return backgroundWidth;
} }
public void setBackgroundHeight(int backgroundHeight) { public void setBackgroundWidth(int backgroundWidth) {
this.backgroundHeight = backgroundHeight; this.backgroundWidth = backgroundWidth <= 0 ? 100 : backgroundWidth; // 防无效值,确保宽高比有效
} }
public int getBackgroundHeight() { public int getBackgroundHeight() {
return backgroundHeight; return backgroundHeight;
} }
@Override public void setBackgroundHeight(int backgroundHeight) {
public String getName() { this.backgroundHeight = backgroundHeight <= 0 ? 100 : backgroundHeight; // 防无效值,确保宽高比有效
return BackgroundBean.class.getName();
} }
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 @Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException { public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter); super.writeThisToJsonWriter(jsonWriter);
BackgroundBean bean = this; BackgroundBean bean = this;
jsonWriter.name("backgroundFileName").value(bean.getBackgroundFileName()); jsonWriter.name("backgroundFileName").value(bean.getBackgroundFileName());
jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo()); jsonWriter.name("backgroundFileInfo").value(bean.getBackgroundFileInfo());
jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile()); jsonWriter.name("isUseBackgroundFile").value(bean.isUseBackgroundFile());
jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName()); jsonWriter.name("backgroundScaledCompressFileName").value(bean.getBackgroundScaledCompressFileName());
jsonWriter.name("isUseScaledCompress").value(bean.isUseScaledCompress()); jsonWriter.name("isUseScaledCompress").value(bean.isUseScaledCompress());
jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth()); jsonWriter.name("backgroundWidth").value(bean.getBackgroundWidth());
jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight()); jsonWriter.name("backgroundHeight").value(bean.getBackgroundHeight());
jsonWriter.name("pixelColor").value(bean.getPixelColor()); jsonWriter.name("pixelColor").value(bean.getPixelColor());
} }
/**
* 从JSON读取数据到Bean反序列化从文件加载
* 确保所有字段都被读取,兼容旧版本(无字段时设默认值)
*/
@Override @Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException { public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
BackgroundBean bean = new BackgroundBean(); BackgroundBean bean = new BackgroundBean();
jsonReader.beginObject(); jsonReader.beginObject();
while (jsonReader.hasNext()) { while (jsonReader.hasNext()) {
String name = jsonReader.nextName(); String name = jsonReader.nextName();
if (name.equals("backgroundFileName")) { switch (name) {
bean.setBackgroundFileName(jsonReader.nextString()); case "backgroundFileName":
} else if (name.equals("backgroundFileInfo")) { bean.setBackgroundFileName(jsonReader.nextString());
bean.setBackgroundFileInfo(jsonReader.nextString()); break;
} else if (name.equals("isUseBackgroundFile")) { case "backgroundFileInfo":
bean.setIsUseBackgroundFile(jsonReader.nextBoolean()); bean.setBackgroundFileInfo(jsonReader.nextString());
} else if (name.equals("backgroundScaledCompressFileName")) { break;
bean.setBackgroundScaledCompressFileName(jsonReader.nextString()); case "isUseBackgroundFile":
} else if (name.equals("isUseScaledCompress")) { bean.setIsUseBackgroundFile(jsonReader.nextBoolean());
bean.setIsUseScaledCompress(jsonReader.nextBoolean()); break;
} else if (name.equals("backgroundWidth")) { case "backgroundScaledCompressFileName":
bean.setBackgroundWidth(jsonReader.nextInt()); bean.setBackgroundScaledCompressFileName(jsonReader.nextString());
} else if (name.equals("backgroundHeight")) { break;
bean.setBackgroundHeight(jsonReader.nextInt()); case "isUseScaledCompress":
} else if (name.equals("pixelColor")) { bean.setIsUseScaledCompress(jsonReader.nextBoolean());
bean.setPixelColor(jsonReader.nextInt()); break;
} else { case "backgroundWidth":
jsonReader.skipValue(); 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(); jsonReader.endObject();
return bean; return bean;
} }
} }

View File

@@ -3,52 +3,48 @@ package cc.winboll.studio.powerbell.utils;
import android.content.Context; import android.content.Context;
import cc.winboll.studio.powerbell.model.BackgroundBean; import cc.winboll.studio.powerbell.model.BackgroundBean;
import java.io.File; import java.io.File;
import java.util.UUID; import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils; import cc.winboll.studio.libappbase.ToastUtils;
/** /**
* @Author ZhanGSKen<zhangsken@qq.com> * @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2024/07/18 12:07:20 * @Date 2024/07/18 12:07:20
* @Describe 背景图片工具集(修复单例模式,线程安全) * @Describe 背景图片工具集(修复单例模式,线程安全+数据流转正常
*/ */
public class BackgroundSourceUtils { public class BackgroundSourceUtils {
public static final String TAG = "BackgroundPictureUtils"; public static final String TAG = "BackgroundPictureUtils";
// 1. 静态实例加volatile禁止指令重排保证可见性 // 1. 静态实例加volatile禁止指令重排保证可见性(双重校验锁单例核心)
private static volatile BackgroundSourceUtils sInstance; private static volatile BackgroundSourceUtils sInstance;
private Context mContext; private Context mContext;
private File currentBackgroundBeanFile; private File currentBackgroundBeanFile;
private BackgroundBean currentBackgroundBean; private BackgroundBean currentBackgroundBean;
private File previewBackgroundBeanFile; private File previewBackgroundBeanFile;
private BackgroundBean previewBackgroundBean; private BackgroundBean previewBackgroundBean;
// 应用外部存储文件夹路径 // 应用外部存储文件夹路径(按功能划分目录,避免路径混乱)
private File fUtilsDir; private File fUtilsDir;
private File fModelDir; private File fModelDir;
// 背景图片目录 // 背景图片源文件目录(存储正式/预览图片)
private File fBackgroundSourceDir; private File fBackgroundSourceDir;
// 2. 私有构造器(加防反射逻辑) // 2. 私有构造器(加防反射逻辑+初始化目录
private BackgroundSourceUtils(Context context) { private BackgroundSourceUtils(Context context) {
// 防反射破坏:若已有实例,抛异常阻止创建 // 防反射破坏:若已有实例,抛异常阻止重复创建
if (sInstance != null) { if (sInstance != null) {
throw new RuntimeException("BackgroundSourceUtils 是单例类,禁止重复创建!"); throw new RuntimeException("BackgroundSourceUtils 是单例类,禁止重复创建!");
} }
// 上下文建议用Application Context避免内存泄漏 // 上下文用Application Context避免Activity内存泄漏
this.mContext = context.getApplicationContext(); this.mContext = context.getApplicationContext();
fUtilsDir = this.mContext.getExternalFilesDir(TAG); // 初始化目录(按功能划分,确保目录存在)
fModelDir = new File(fUtilsDir, "ModelDir"); initDirs();
currentBackgroundBeanFile = new File(fModelDir, "currentBackgroundBean.json"); // 加载配置(正式/预览Bean
previewBackgroundBeanFile = new File(fModelDir, "previewBackgroundBean.json");
fBackgroundSourceDir = new File(fUtilsDir, "BackgroundSource");
// 加载配置
loadSettings(); loadSettings();
} }
// 3. 双重校验锁单例(线程安全,高效) // 3. 双重校验锁单例(线程安全,高效,支持多线程并发调用
public static BackgroundSourceUtils getInstance(Context context) { public static BackgroundSourceUtils getInstance(Context context) {
// 第一重校验:避免每次调用都加锁(提效率) // 第一重校验:避免每次调用都加锁(提效率)
if (sInstance == null) { if (sInstance == null) {
// 同步锁:保证同一时刻只有一个线程进入创建逻辑 // 同步锁:保证同一时刻只有一个线程进入创建逻辑
synchronized (BackgroundSourceUtils.class) { synchronized (BackgroundSourceUtils.class) {
@@ -61,84 +57,202 @@ public class BackgroundSourceUtils {
return sInstance; 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() { void loadSettings() {
// 加载正式Bean
currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class); currentBackgroundBean = BackgroundBean.loadBeanFromFile(currentBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
if (currentBackgroundBean == null) { if (currentBackgroundBean == null) {
currentBackgroundBean = new BackgroundBean(); currentBackgroundBean = new BackgroundBean();
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); 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) { if (previewBackgroundBean == null) {
previewBackgroundBean = new BackgroundBean(); previewBackgroundBean = new BackgroundBean();
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "预览背景Bean不存在创建默认Bean");
} }
} }
public BackgroundBean getCurrentBackgroundBean() {
return currentBackgroundBean;
}
public BackgroundBean getPreviewBackgroundBean() {
return previewBackgroundBean;
}
public String getCurrentBackgroundFilePath() { /**
loadSettings(); * 获取正式背景Bean对外提供用于修改正式配置
File file = new File(fBackgroundSourceDir, currentBackgroundBean.getBackgroundFileName()); */
return file.getAbsolutePath(); public BackgroundBean getCurrentBackgroundBean() {
} return currentBackgroundBean;
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 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() { public String getBackgroundSourceDirPath() {
return fBackgroundSourceDir.getAbsolutePath(); return fBackgroundSourceDir.getAbsolutePath();
} }
/* /**
* 保存图片到预览模型, 并返回预览模型数据 * 保存图片到预览Bean核心修复解决文件名覆盖+路径无效问题)
*/ * @param sourceFile 源图片文件(非空,必须存在)
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) { * @param fileInfo 图片附加信息如Uri字符串仅作备注
File previewBackgroundFile = new File(fBackgroundSourceDir, FileUtils.createUniqueFileName(sourceFile)); * @return 更新后的预览Bean
//ToastUtils.show(String.format("saveFileToPreviewBean previewBackgroundFile : %s", previewBackgroundFile.getAbsolutePath())); */
public BackgroundBean saveFileToPreviewBean(File sourceFile, String fileInfo) {
FileUtils.copyFile(sourceFile, previewBackgroundFile); // 校验源文件合法性
previewBackgroundBean = new BackgroundBean(); if (sourceFile == null || !sourceFile.exists() || !sourceFile.isFile()) {
previewBackgroundBean.setBackgroundFileName(previewBackgroundFile.getName()); LogUtils.e(TAG, "源文件无效:" + (sourceFile != null ? sourceFile.getAbsolutePath() : "null"));
previewBackgroundBean.setBackgroundScaledCompressFileName("ScaledCompress_"+previewBackgroundFile.getName()); ToastUtils.show("源图片文件无效");
previewBackgroundBean.setBackgroundFileName(fileInfo); return previewBackgroundBean;
saveSettings(); }
ToastUtils.show(String.format("saveFileToPreviewBean getPreviewBackgroundFilePath() : %s", getPreviewBackgroundFilePath())); // 确保背景目录存在(防止首次使用时目录未创建)
if (!fBackgroundSourceDir.exists()) {
return previewBackgroundBean; fBackgroundSourceDir.mkdirs();
} LogUtils.d(TAG, "背景目录不存在,自动创建:" + fBackgroundSourceDir.getAbsolutePath());
}
public void commitPreviewSourceToCurrent() {
currentBackgroundBean = previewBackgroundBean; // 生成唯一文件名(基于源文件后缀,避免重复)
saveSettings(); String uniqueFileName = FileUtils.createUniqueFileName(sourceFile);
} File previewBackgroundFile = new File(fBackgroundSourceDir, uniqueFileName);
public void setCurrentSourceToPreview() { // 复制源文件到预览目录(确保图片实际保存成功)
previewBackgroundBean = currentBackgroundBean; boolean copySuccess = FileUtils.copyFile(sourceFile, previewBackgroundFile);
saveSettings(); 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, "正式背景同步到预览:预览背景更新为当前正式背景");
}
} }

View File

@@ -10,33 +10,28 @@ import android.util.AttributeSet;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R; import cc.winboll.studio.powerbell.model.BackgroundBean;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils; import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
import cc.winboll.studio.libappbase.ToastUtils; import java.io.File;
/** /**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com> * @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:01 * @Date 2025/11/19 18:01
* @Describe 背景图片视图控件(全透明背景 + 不拉伸居中平铺 + 完全填充父视图) * @Describe 背景图片视图控件(全透明背景 + 不拉伸居中平铺 + 完全填充父视图 + 预览/正式模式切换
*/ */
public class BackgroundView extends RelativeLayout { public class BackgroundView extends RelativeLayout {
public static final String TAG = "BackgroundView"; public static final String TAG = "BackgroundView";
Context mContext; private Context mContext;
private ImageView ivBackground; 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 float imageAspectRatio = 1.0f; // 图片原始宽高比(控制不拉伸)
// 标记当前是否处于预览模式 // 标记当前是否处于预览模式(用于区分加载预览/正式背景)
private boolean isPreviewMode = false; private boolean isPreviewMode = false;
// 构造器(兼容所有布局场景)
public BackgroundView(Context context) { public BackgroundView(Context context) {
super(context); super(context);
this.mContext = context; this.mContext = context;
@@ -61,223 +56,134 @@ public class BackgroundView extends RelativeLayout {
initView(); initView();
} }
void initView() { /**
// 1. 控件本身:完全填充父视图 + 全透明背景 + 无内边距 * 初始化视图(控件本身+内部ImageView
*/
private void initView() {
// 1. 控件本身配置:完全填充父视图 + 全透明背景 + 无内边距
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
setPadding(0, 0, 0, 0); // 取消自身内边距 setPadding(0, 0, 0, 0); // 取消自身内边距(避免父容器与控件间缝隙)
setBackgroundColor(0x00000000); // 全透明背景(#00000000 setBackgroundColor(0x00000000); // 全透明背景(ARGB透明+黑色,无视觉影响
setBackground(new ColorDrawable(0x00000000)); // 双重保障:同时设置Background为透明兼容低版本 setBackground(new ColorDrawable(0x00000000)); // 双重保障:兼容Android低版本确保背景透明
initBackgroundImageView(); // 初始化内部ImageView全透明 + 不拉伸居中平铺 // 初始化工具类(单例,全局唯一
backgroundSourceUtils = BackgroundSourceUtils.getInstance(mContext);
backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getCurrentBackgroundFilePath(); // 2. 初始化内部ImageView核心不拉伸+居中平铺+全透明)
loadAndSetImageViewBackground(); initBackgroundImageView();
// 3. 初始加载:默认加载正式背景
reloadCurrentBackground();
} }
/**
* 初始化内部ImageView配置尺寸、缩放模式、背景等
*/
private void initBackgroundImageView() { private void initBackgroundImageView() {
ivBackground = new ImageView(mContext); ivBackground = new ImageView(mContext);
// 2. ImageView初始宽高WRAP_CONTENT + 居中 + 无内边距 + 全透明背景 // 基础布局:宽高WRAP_CONTENT(跟随图片比例)+ 居中显示 + 无内边距/边距
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); // 居中显示 layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT); // 核心ImageView在控件中居中
layoutParams.setMargins(0, 0, 0, 0); // 取消边距 layoutParams.setMargins(0, 0, 0, 0); // 取消边距避免ImageView与控件间缝隙
ivBackground.setLayoutParams(layoutParams); ivBackground.setLayoutParams(layoutParams);
// 3. 缩放模式FIT_CENTER不拉伸按比例显示 // 关键配置:缩放模式FIT_CENTER不拉伸原始比例显示)
ivBackground.setScaleType(ImageView.ScaleType.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.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();
// }
// }
/** /**
* 拷贝图片文件到背景资源目录(正式背景 * 【对外提供】重新加载正式背景图片从正式Bean获取路径
*/ * 用于:退出预览模式、恢复默认背景、正式背景更新后刷新
// 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, "退出预览模式,恢复正式背景");
}
}*/
/**
* 公共函数:供外部类调用,重新加载正式背景图片(刷新显示)
*/ */
public void reloadCurrentBackground() { public void reloadCurrentBackground() {
LogUtils.d(TAG, "外部调用重新加载背景图片"); LogUtils.d(TAG, "=== 开始重新加载正式背景 ===");
backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getCurrentBackgroundFilePath(); isPreviewMode = false; // 标记为正式模式
loadAndSetImageViewBackground(); // 从工具类获取最新正式背景路径(确保路径同步)
String backgroundPath = backgroundSourceUtils.getCurrentBackgroundFilePath();
// 加载并显示图片
loadAndSetImageViewBackground(backgroundPath);
} }
/**
* 【对外提供】重新加载预览背景图片从预览Bean获取路径
* 修复增加isUseBackgroundFile校验确保启用背景图时才加载
*/
public void reloadPreviewBackground() { public void reloadPreviewBackground() {
LogUtils.d(TAG, "外部调用重新加载背景图片"); LogUtils.d(TAG, "=== 开始重新加载预览背景 ===");
backgroundSourceFilePath = BackgroundSourceUtils.getInstance(this.mContext).getPreviewBackgroundFilePath(); isPreviewMode = true; // 标记为预览模式
loadAndSetImageViewBackground(); // 从工具类获取最新预览背景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() { private void loadAndSetImageViewBackground(String imagePath) {
if (backgroundSourceFilePath == null) { // 1. 路径校验(空路径/无效路径 → 显示透明背景)
LogUtils.d(TAG, "backgroundSourceFilePath == null"); if (imagePath == null || imagePath.isEmpty()) {
setDefaultImageViewBackground(); LogUtils.e(TAG, "图片路径为空,显示透明背景");
setDefaultTransparentBackground();
return; return;
} }
//ToastUtils.show(String.format("backgroundSourceFilePath : %s", backgroundSourceFilePath));
File backgroundFile = new File(backgroundSourceFilePath); // 2. 文件校验(文件不存在/不是文件 → 显示透明背景)
File backgroundFile = new File(imagePath);
if (!backgroundFile.exists() || !backgroundFile.isFile()) { if (!backgroundFile.exists() || !backgroundFile.isFile()) {
LogUtils.e(TAG, "背景图片不存在" + backgroundSourceFilePath); LogUtils.e(TAG, "图片文件不存在或无效" + imagePath);
setDefaultImageViewBackground(); setDefaultTransparentBackground();
return; return;
} }
// 计算图片原始宽高比 // 3. 计算图片原始宽高比(确保不拉伸)
if (!calculateImageAspectRatio(backgroundFile)) { if (!calculateImageAspectRatio(backgroundFile)) {
setDefaultImageViewBackground(); LogUtils.e(TAG, "图片尺寸无效,无法加载");
setDefaultTransparentBackground();
return; return;
} }
// 压缩加载 Bitmap保持比例) // 4. 压缩加载Bitmap避免OOM保持原始比例)
Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920);
if (bitmap == null) { if (bitmap == null) {
LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap"); LogUtils.e(TAG, "图片压缩加载失败" + imagePath);
setDefaultImageViewBackground(); setDefaultTransparentBackground();
return; return;
} }
// 设置图片(保持ImageView透明背景 // 5. 设置图片ImageView(保持透明背景)
Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap); Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
ivBackground.setBackground(backgroundDrawable); ivBackground.setBackground(backgroundDrawable);
@@ -285,43 +191,51 @@ public class BackgroundView extends RelativeLayout {
ivBackground.setBackgroundDrawable(backgroundDrawable); ivBackground.setBackgroundDrawable(backgroundDrawable);
} }
// 调整ImageView尺寸居中平铺不拉伸 // 6. 调整ImageView尺寸居中平铺适配控件大小
adjustImageViewSize(); 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) { private boolean calculateImageAspectRatio(File file) {
try { try {
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true; // 仅获取尺寸,不加载图片(省内存)
BitmapFactory.decodeFile(file.getAbsolutePath(), options); BitmapFactory.decodeFile(file.getAbsolutePath(), options);
int imageWidth = options.outWidth; int imageWidth = options.outWidth;
int imageHeight = options.outHeight; int imageHeight = options.outHeight;
// 校验尺寸有效性(宽/高必须大于0
if (imageWidth <= 0 || imageHeight <= 0) { if (imageWidth <= 0 || imageHeight <= 0) {
LogUtils.e(TAG, "图片尺寸无效:宽=" + imageWidth + ", 高=" + imageHeight); LogUtils.e(TAG, "图片尺寸无效:宽=" + imageWidth + ", 高=" + imageHeight);
return false; return false;
} }
// 保存原始宽高比
imageAspectRatio = (float) imageWidth / imageHeight; imageAspectRatio = (float) imageWidth / imageHeight;
return true; return true;
} catch (Exception e) { } catch (Exception e) {
LogUtils.e(TAG, "计算图片宽高比失败:" + e.getMessage()); LogUtils.e(TAG, "计算图片宽高比失败:" + e.getMessage(), e);
return false; return false;
} }
} }
/** /**
* 调整ImageView尺寸不拉伸居中平铺 * 调整ImageView尺寸核心:不拉伸,居中平铺,适配控件大小
* 效果ImageView按图片比例缩放最大尺寸不超过控件同时在控件中居中
*/ */
private void adjustImageViewSize() { private void adjustImageViewSize() {
int parentWidth = getWidth(); // 控件宽度(已填充父视图) int parentWidth = getWidth(); // 控件宽度(已完全填充父视图)
int parentHeight = getHeight(); // 控件高度(已填充父视图) int parentHeight = getHeight(); // 控件高度(已完全填充父视图)
// 若父容器未测量完成(宽度/高度为0延迟调整避免尺寸计算错误
if (parentWidth == 0 || parentHeight == 0) { if (parentWidth == 0 || parentHeight == 0) {
post(new Runnable() { post(new Runnable() {
@Override @Override
@@ -329,72 +243,86 @@ public class BackgroundView extends RelativeLayout {
adjustImageViewSize(); adjustImageViewSize();
} }
}); });
LogUtils.d(TAG, "父容器未测量完成延迟调整ImageView尺寸");
return; return;
} }
int imageViewWidth, imageViewHeight; int imageViewWidth, imageViewHeight;
if (imageAspectRatio >= 1.0f) { // 横图 // 按图片原始比例计算ImageView最大可行尺寸不超过控件不拉伸
if (imageAspectRatio >= 1.0f) { // 横图(宽 ≥ 高):优先适配控件宽度
imageViewWidth = Math.min(parentWidth, (int) (parentHeight * imageAspectRatio)); imageViewWidth = Math.min(parentWidth, (int) (parentHeight * imageAspectRatio));
imageViewHeight = (int) (imageViewWidth / imageAspectRatio); imageViewHeight = (int) (imageViewWidth / imageAspectRatio);
} else { // 竖图 } else { // 竖图(宽 < 高):优先适配控件高度
imageViewHeight = Math.min(parentHeight, (int) (parentWidth / imageAspectRatio)); imageViewHeight = Math.min(parentHeight, (int) (parentWidth / imageAspectRatio));
imageViewWidth = (int) (imageViewHeight * imageAspectRatio); imageViewWidth = (int) (imageViewHeight * imageAspectRatio);
} }
// 应用尺寸 // 应用尺寸到ImageView更新布局参数
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivBackground.getLayoutParams(); RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivBackground.getLayoutParams();
layoutParams.width = imageViewWidth; layoutParams.width = imageViewWidth;
layoutParams.height = imageViewHeight; layoutParams.height = imageViewHeight;
ivBackground.setLayoutParams(layoutParams); 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 压缩后的Bitmapnull表示失败
*/ */
private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) { private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) {
try { try {
BitmapFactory.Options options = new BitmapFactory.Options(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; options.inJustDecodeBounds = true; // 先获取图片尺寸
BitmapFactory.decodeFile(file.getAbsolutePath(), options); BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// 计算压缩比例(仅缩小,不放大,保持比例)
int scaleX = options.outWidth / maxWidth; int scaleX = options.outWidth / maxWidth;
int scaleY = options.outHeight / maxHeight; int scaleY = options.outHeight / maxHeight;
int inSampleSize = Math.max(scaleX, scaleY); int inSampleSize = Math.max(scaleX, scaleY);
if (inSampleSize <= 0) { if (inSampleSize <= 0) {
inSampleSize = 1; inSampleSize = 1; // 最小压缩比例为1不压缩
} }
// 正式解码图片(压缩+省内存配置)
options.inJustDecodeBounds = false; options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize; options.inSampleSize = inSampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; options.inPreferredConfig = Bitmap.Config.RGB_565; // 比ARGB_8888省一半内存
return BitmapFactory.decodeFile(file.getAbsolutePath(), options); return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
} catch (Exception e) { } catch (Exception e) {
LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage()); LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage(), e);
return null; return null;
} }
} }
/** /**
* 设置默认背景(全透明兜底,避免空白 * 设置默认透明背景(图片加载失败/路径无效时兜底
* 确保:无图片时控件完全透明,不遮挡下层视图
*/ */
private void setDefaultImageViewBackground() { private void setDefaultTransparentBackground() {
// 关键:默认背景设为全透明(而非默认图,避免遮挡下层视图)
ivBackground.setBackground(new ColorDrawable(0x00000000)); // 全透明背景 ivBackground.setBackground(new ColorDrawable(0x00000000)); // 全透明背景
imageAspectRatio = 1.0f; imageAspectRatio = 1.0f; // 重置宽高比(避免影响下次加载)
adjustImageViewSize(); adjustImageViewSize(); // 调整尺寸确保ImageView居中且不占位异常
LogUtils.d(TAG, "已设置默认透明背景"); LogUtils.d(TAG, "已设置默认透明背景");
} }
/**
* 父容器尺寸变化时如屏幕旋转重新调整ImageView尺寸
* 确保:控件尺寸变化后,图片仍保持不拉伸+居中平铺
*/
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
adjustImageViewSize(); LogUtils.d(TAG, "控件尺寸变化:旧尺寸=" + oldw + "x" + oldh + ",新尺寸=" + w + "x" + h);
adjustImageViewSize(); // 重新调整ImageView尺寸
} }
/** /**
* 对外提供判断当前是否处于预览模式 * 对外提供判断当前是否处于预览模式
* 用于Activity中判断是否需要提交预览背景
*/ */
public boolean isPreviewMode() { public boolean isPreviewMode() {
return isPreviewMode; return isPreviewMode;