diff --git a/powerbell/build.gradle b/powerbell/build.gradle
index 7a491c55..c1f35a6c 100644
--- a/powerbell/build.gradle
+++ b/powerbell/build.gradle
@@ -56,7 +56,12 @@ dependencies {
api 'com.github.bumptech.glide:glide:4.9.0'
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
-
+ // uCrop 核心依赖(最新稳定版)
+ implementation 'com.github.yalantis:ucrop:2.2.8'
+ // 兼容AndroidX(若项目用AndroidX,必须添加)
+ //implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'androidx.exifinterface:exifinterface:1.3.6'
+
// 应用介绍页类库
api 'io.github.medyo:android-about-page:2.0.0'
// SSH
diff --git a/powerbell/build.properties b/powerbell/build.properties
index bbd31512..d83c9e80 100644
--- a/powerbell/build.properties
+++ b/powerbell/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Thu Dec 04 05:39:23 GMT 2025
+#Thu Dec 04 08:21:41 GMT 2025
stageCount=13
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
-buildCount=205
+buildCount=229
baseBetaVersion=15.11.13
diff --git a/powerbell/src/main/AndroidManifest.xml b/powerbell/src/main/AndroidManifest.xml
index 4da18dab..af1dd9ae 100644
--- a/powerbell/src/main/AndroidManifest.xml
+++ b/powerbell/src/main/AndroidManifest.xml
@@ -232,6 +232,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 f626498e..7d376259 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
@@ -454,6 +454,41 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
ToastUtils.show("拍照图片解析失败");
return null;
}
+
+ private void handleSelectPictureResult(int resultCode, Intent data) {
+ if (resultCode != RESULT_OK || data == null) {
+ handleOperationCancelOrFail();
+ return;
+ }
+
+ Uri selectedImage = data.getData();
+ if (selectedImage == null) {
+ ToastUtils.show("图片Uri为空");
+ return;
+ }
+ LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString());
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ getContentResolver().takePersistableUriPermission(
+ selectedImage,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION
+ );
+ LogUtils.d(TAG, "【选图权限】已添加持久化权限");
+ }
+
+ mBgSourceUtils.createCropFileProviderBackgroundBean(selectedImage);
+
+ LogUtils.d(TAG, "【选图同步】路径绑定完成");
+ // 选图后启动固定比例裁剪(调用工具类)
+ mBgSourceUtils.loadSettings();
+ startSystemCrop(
+ mBgSourceUtils.getPreviewBackgroundBean(),
+ mBackgroundView.getWidth(),
+ mBackgroundView.getHeight(),
+ false,
+ REQUEST_CROP_IMAGE
+ );
+ }
private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果");
@@ -658,46 +693,13 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
}, 50);
}
- private void handleSelectPictureResult(int resultCode, Intent data) {
- if (resultCode != RESULT_OK || data == null) {
- handleOperationCancelOrFail();
- return;
- }
-
- Uri selectedImage = data.getData();
- if (selectedImage == null) {
- ToastUtils.show("图片Uri为空");
- return;
- }
- LogUtils.d(TAG, "【选图回调】Uri : " + selectedImage.toString());
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- getContentResolver().takePersistableUriPermission(
- selectedImage,
- Intent.FLAG_GRANT_READ_URI_PERMISSION
- );
- LogUtils.d(TAG, "【选图权限】已添加持久化权限");
- }
-
- BackgroundBean cropBean = mBgSourceUtils.createCropFileProviderBackgroundBean(selectedImage);
-
- LogUtils.d(TAG, "【选图同步】路径绑定完成");
- // 选图后启动固定比例裁剪(调用工具类)
- startSystemCrop(
- cropBean,
- mBackgroundView.getWidth(),
- mBackgroundView.getHeight(),
- false,
- REQUEST_CROP_IMAGE
- );
- }
private void handleOperationCancelOrFail() {
initBackgroundViewByPreviewBean();
LogUtils.d(TAG, "【操作回调】取消或失败");
ToastUtils.show("操作已取消");
}
-
+
/**
* 启动系统裁剪工具
@@ -708,58 +710,16 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
* @param isFreeCrop 是否自由裁剪
* @param requestCode 裁剪请求码
*/
- public void startSystemCrop(BackgroundBean cropBean, int aspectX, int aspectY, boolean isFreeCrop, int requestCode) {
-
- LogUtils.d(TAG, "startSystemCrop: 启动系统裁剪,自由裁剪:" + isFreeCrop);
- File srcFile = new File(cropBean.getBackgroundFilePath());
-
- // 校验原图
- if (srcFile == null || !srcFile.exists() || srcFile.length() <= 100) {
- Toast.makeText(this, "无有效图片可裁剪", Toast.LENGTH_SHORT).show();
- LogUtils.e(TAG, "startSystemCrop: 原图无效");
- return;
- }
-
- // 生成输入Uri
- Uri inputUri = getFileProviderUri(srcFile);
- if (inputUri == null) {
- Toast.makeText(this, "获取图片Uri失败", Toast.LENGTH_SHORT).show();
- LogUtils.e(TAG, "startSystemCrop: 输入Uri生成失败");
- return;
- }
-
- // 生成输出文件(使用BackgroundSourceUtils的压缩路径)
- BackgroundSourceUtils bgSourceUtils = BackgroundSourceUtils.getInstance(this);
- File outputFile = new File(bgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
- if (outputFile == null) {
- Toast.makeText(this, "裁剪输出路径无效", Toast.LENGTH_SHORT).show();
- LogUtils.e(TAG, "startSystemCrop: 输出文件为空");
- return;
- }
- Uri outputUri = getFileProviderUri(outputFile);
-
-
- Uri cropOutPutUri = outputUri;
-
- Intent intent = new Intent("com.android.camera.action.CROP");
- intent.setDataAndType(inputUri, "image/*");
- //intent.setDataAndType(inputUri, activity.getContentResolver().getType(inputUri));
- intent.putExtra("crop", "true");
- intent.putExtra("noFaceDetection", true);
-
- if (!isFreeCrop) {
- intent.putExtra("aspectX", aspectX);
- intent.putExtra("aspectY", aspectY);
- }
-
- intent.putExtra("return-data", true);
- intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri);
- intent.putExtra("scale", true);
- intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- startActivityForResult(intent, requestCode);
- LogUtils.d(TAG, "startSystemCrop: 启动裁剪成功,输出路径:" + outputFile.getAbsolutePath());
- }
+ /**
+ * 启动系统裁剪工具
+ * @param activity 上下文
+ * @param srcFile 裁剪原图
+ * @param aspectX 裁剪宽比例(自由裁剪传0)
+ * @param aspectY 裁剪高比例(自由裁剪传0)
+ * @param isFreeCrop 是否自由裁剪
+ * @param requestCode 裁剪请求码
+ */
+
/**
* 获取FileProvider Uri(复用方法,避免重复代码)
@@ -788,9 +748,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override
public void finish() {
- if(isCommitSettings) {
+ if (isCommitSettings) {
super.finish();
- } else{
+ } else {
YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
@@ -807,5 +767,72 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
});
}
}
+
+ public void startSystemCrop(BackgroundBean cropBean, int aspectX, int aspectY, boolean isFreeCrop, int requestCode) {
+
+ LogUtils.d(TAG, "startSystemCrop: 启动系统裁剪,自由裁剪:" + isFreeCrop);
+ File srcFile = new File(cropBean.getBackgroundFilePath());
+
+ // 校验原图
+ if (srcFile == null || !srcFile.exists() || srcFile.length() <= 100) {
+ Toast.makeText(this, "无有效图片可裁剪", Toast.LENGTH_SHORT).show();
+ LogUtils.e(TAG, "startSystemCrop: 原图无效");
+ return;
+ }
+
+ // 生成输入Uri
+ Uri inputUri = getFileProviderUri(srcFile);
+ if (inputUri == null) {
+ Toast.makeText(this, "获取图片Uri失败", Toast.LENGTH_SHORT).show();
+ LogUtils.e(TAG, "startSystemCrop: 输入Uri生成失败");
+ return;
+ }
+
+ // 生成输出文件(使用BackgroundSourceUtils的压缩路径)
+ BackgroundSourceUtils bgSourceUtils = BackgroundSourceUtils.getInstance(this);
+ bgSourceUtils.loadSettings();
+ File outputFile = new File(bgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
+ if (outputFile == null) {
+ Toast.makeText(this, "裁剪输出路径无效", Toast.LENGTH_SHORT).show();
+ LogUtils.e(TAG, "startSystemCrop: 输出文件为空");
+ return;
+ }
+ Uri outputUri = getFileProviderUri(outputFile);
+
+
+ Uri cropOutPutUri = outputUri;
+
+ Intent intent = new Intent("com.android.camera.action.CROP");
+ intent.setDataAndType(inputUri, "image/*");
+ //intent.setDataAndType(inputUri, activity.getContentResolver().getType(inputUri));
+ intent.putExtra("crop", "true");
+ intent.putExtra("noFaceDetection", true);
+
+ if (!isFreeCrop) {
+ intent.putExtra("aspectX", aspectX);
+ intent.putExtra("aspectY", aspectY);
+ }
+
+ // 修复:删除return-data=true,避免与EXTRA_OUTPUT冲突
+ // intent.putExtra("return-data", true);
+ // 修复1:明确禁用return-data,强制写入文件(关键)
+ intent.putExtra("return-data", false);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, cropOutPutUri);
+ intent.putExtra("scale", true);
+ // 修复2:启用缩放补全,确保图片适配输出尺寸(兼容性)
+ intent.putExtra("scaleUpIfNeeded", true);
+ intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
+ // 修复3:授予读写权限,并添加FLAG_GRANT_PERSISTABLE_URI_PERMISSION(适配Android 11+写入权限)
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ // 修复4:对输出目录授予临时写入权限(解决目录无写入权限问题)
+ grantUriPermission("com.android.camera", outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+ startActivityForResult(intent, requestCode);
+ LogUtils.d(TAG, "startSystemCrop: 启动裁剪成功,输入路径:" + srcFile.getAbsolutePath() + ",输出路径:" + outputFile.getAbsolutePath());
+ }
+
+
}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java
deleted file mode 100644
index 7504073b..00000000
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package cc.winboll.studio.powerbell.unittest;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import androidx.fragment.app.Fragment;
-import cc.winboll.studio.libappbase.GlobalApplication;
-import cc.winboll.studio.libappbase.ToastUtils;
-import cc.winboll.studio.powerbell.R;
-import android.widget.Button;
-import cc.winboll.studio.powerbell.MainActivity;
-import android.content.Intent;
-import cc.winboll.studio.powerbell.model.BackgroundBean;
-import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
-import cc.winboll.studio.powerbell.views.BackgroundView;
-
-/**
- * @Author ZhanGSKen&豆包大模型
- * @Date 2025/11/19 18:16
- * @Describe BackgroundViewTestFragment
- */
-public class BackgroundViewTestFragment extends Fragment {
-
- public static final String TAG = "BackgroundViewTestFragment";
-
- View mainView;
- BackgroundSourceUtils mBgSourceUtils;
- BackgroundView mBackgroundView;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- //super.onCreateView(inflater, container, savedInstanceState);
-
- mBgSourceUtils = BackgroundSourceUtils.getInstance(getActivity());
- mBgSourceUtils.loadSettings();
-
- mainView = inflater.inflate(R.layout.fragment_test_backgroundview, container, false);
- mBackgroundView = mainView.findViewById(R.id.backgroundview);
-
- ((Button)mainView.findViewById(R.id.btn_main_activity)).setOnClickListener(new View.OnClickListener(){
-
- @Override
- public void onClick(View v) {
- getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
- }
- });
-
- loadBackground();
- ToastUtils.show(String.format("%s onCreate", TAG));
- return mainView;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- loadBackground();
- }
-
-
-
- void loadBackground() {
- mBackgroundView.loadImage("/storage/emulated/0/Pictures/Gallery/owner/素材/1626915857361.png");
- }
-}
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/MainUnitTestActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/MainUnitTestActivity.java
index a5c91248..367785d9 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/MainUnitTestActivity.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/MainUnitTestActivity.java
@@ -1,14 +1,23 @@
package cc.winboll.studio.powerbell.unittest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Bundle;
-import android.widget.FrameLayout;
+import android.os.Environment;
+import android.view.View;
+import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;
-import cc.winboll.studio.libappbase.GlobalApplication;
-import cc.winboll.studio.powerbell.R;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
-import android.nfc.tech.TagTechnology;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
+import cc.winboll.studio.powerbell.MainActivity;
+import cc.winboll.studio.powerbell.R;
+import cc.winboll.studio.powerbell.utils.BackgroundSourceUtils;
+import cc.winboll.studio.powerbell.utils.ImageCropUtils;
+import cc.winboll.studio.powerbell.views.BackgroundView;
+import java.io.File;
/**
* @Author ZhanGSKen&豆包大模型
@@ -18,22 +27,157 @@ import cc.winboll.studio.libappbase.ToastUtils;
public class MainUnitTestActivity extends AppCompatActivity {
public static final String TAG = "MainUnitTestActivity";
-
+ public static final int REQUEST_CROP_IMAGE = 0;
+ // 新增:权限请求码
+ public static final int REQUEST_STORAGE_PERMISSION = 1001;
+ View mainView;
+ BackgroundSourceUtils mBgSourceUtils;
+ BackgroundView mBackgroundView;
+ // 测试图片路径(用Environment获取,适配低版本,避免硬编码)
+ String szTestSource = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ + "/PowerBell/unittest/2ae9dc9e-7a73-49d4-840a-7ff1712d868c1764798674763.jpeg";
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // 非调试状态就退出
- if (!GlobalApplication.isDebugging()) {
- finish();
- }
+
+ mBgSourceUtils = BackgroundSourceUtils.getInstance(this);
+ mBgSourceUtils.loadSettings();
+
setContentView(R.layout.activity_mainunittest);
- FragmentManager fragmentManager = getSupportFragmentManager();
- FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
- fragmentTransaction.add(R.id.activitymainunittestFrameLayout1, new BackgroundViewTestFragment(), BackgroundViewTestFragment.TAG);
- fragmentTransaction.commit();
+ mBackgroundView = findViewById(R.id.backgroundview);
+
+ ((Button)findViewById(R.id.btn_main_activity)).setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(MainUnitTestActivity.this, MainActivity.class));
+ }
+ });
+
+ // 裁剪测试按钮点击事件(新增权限校验)
+ ((Button)findViewById(R.id.btn_test_cropimage)).setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View v) {
+ ToastUtils.show("onClick:准备启动裁剪");
+ LogUtils.d(TAG, "【裁剪测试】点击裁剪按钮,校验权限");
+
+ // 修复1:移除高版本API依赖,适配低版本存储权限校验
+ if (checkStoragePermission()) {
+ // 权限已授予,启动裁剪
+ startCropTest();
+ } else {
+ // 权限未授予,申请权限
+ requestStoragePermission();
+ }
+ }
+ });
- ToastUtils.show(String.format("%s onCreate", TAG));
+ ToastUtils.show(String.format("%s onCreate", TAG));
+ // 加载测试图片(验证图片路径是否有效)
+ loadBackground();
}
+ /**
+ * 启动裁剪测试(抽取为单独方法,便于权限回调后调用)
+ */
+ private void startCropTest() {
+ // 修复2:输出路径用Environment获取,确保目录存在(避免路径无效)
+ File outputDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ + "/PowerBell/unittest/");
+ if (!outputDir.exists()) {
+ outputDir.mkdirs(); // 创建目录(避免输出路径不存在导致裁剪失败)
+ LogUtils.d(TAG, "【裁剪测试】创建输出目录:" + outputDir.getAbsolutePath());
+ }
+ String dstOutputPath = outputDir.getAbsolutePath()
+ + "/SelectCompress_2ae9dc9e-7a73-49d4-840a-7ff1712d868c1764798674763.jpeg";
+
+ // 修复3:自由裁剪时比例传0(避免100:100过大导致机型崩溃)
+ ImageCropUtils.startImageCrop(
+ MainUnitTestActivity.this,
+ new File(szTestSource),
+ new File(dstOutputPath),
+ 0, // 自由裁剪传0
+ 0, // 自由裁剪传0
+ true,
+ REQUEST_CROP_IMAGE
+ );
+ }
+
+ /**
+ * 校验存储读写权限(适配Android 6.0+ 低版本SDK,移除TIRAMISU依赖)
+ */
+ private boolean checkStoragePermission() {
+ // 适配Android 6.0(API 23)及以上,用通用的读写权限(移除高版本API)
+ return ContextCompat.checkSelfPermission(this, android.Manifest.permission.READ_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED
+ && ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * 申请存储读写权限(适配低版本SDK,移除READ_MEDIA_IMAGES依赖)
+ */
+ private void requestStoragePermission() {
+ LogUtils.d(TAG, "【裁剪测试】申请存储读写权限");
+ // 用通用的读写权限(适配所有Android 6.0+ 机型,无高版本依赖)
+ ActivityCompat.requestPermissions(
+ this,
+ new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ REQUEST_STORAGE_PERMISSION
+ );
+ }
+
+ /**
+ * 权限申请回调
+ */
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (requestCode == REQUEST_STORAGE_PERMISSION) {
+ // 校验权限是否授予
+ boolean allGranted = true;
+ for (int result : grantResults) {
+ if (result != PackageManager.PERMISSION_GRANTED) {
+ allGranted = false;
+ break;
+ }
+ }
+ if (allGranted) {
+ ToastUtils.show("存储权限已授予,启动裁剪");
+ startCropTest(); // 权限授予后启动裁剪
+ } else {
+ ToastUtils.show("存储权限被拒绝,无法启动裁剪");
+ LogUtils.e(TAG, "【裁剪测试】存储权限被拒绝");
+ }
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ LogUtils.d(TAG, "【裁剪回调】requestCode:" + requestCode + ",resultCode:" + resultCode + ",data:" + (data == null ? "null" : data.toString()));
+ ToastUtils.show(String.format("requestCode %d, resultCode %d, data is %s",requestCode, resultCode, data == null));
+ // 裁剪完成后回收权限
+ if (requestCode == REQUEST_CROP_IMAGE) {
+ String dstOutputPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
+ + "/PowerBell/unittest/SelectCompress_2ae9dc9e-7a73-49d4-840a-7ff1712d868c1764798674763.jpeg";
+ //Uri outputUri = ImageCropUtils.getFileProviderUriPublic(this, new File(dstOutputPath));
+ //ImageCropUtils.releaseCropPermission(this, outputUri);
+ mBackgroundView.loadImage(dstOutputPath);
+ }
+ }
+
+ void loadBackground() {
+ // 校验测试图片是否存在(避免路径错误)
+ File testFile = new File(szTestSource);
+ if (testFile.exists() && testFile.length() > 100) {
+ mBackgroundView.loadImage(szTestSource);
+ LogUtils.d(TAG, "【图片加载】测试图片加载成功:" + szTestSource);
+ } else {
+ ToastUtils.show("测试图片不存在或无效");
+ LogUtils.e(TAG, "【图片加载】测试图片无效:" + szTestSource);
+ }
+ }
}
+
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 5979571b..91c4e4c4 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
@@ -245,9 +245,10 @@ public class BackgroundSourceUtils {
return contentUri;
}
- public BackgroundBean createCropFileProviderBackgroundBean(Uri uri) {
+ public boolean createCropFileProviderBackgroundBean(Uri uri) {
InputStream is = null;
FileOutputStream fos = null;
+ loadSettings();
try {
clearCropTempFiles();
@@ -265,7 +266,7 @@ public class BackgroundSourceUtils {
is = mContext.getContentResolver().openInputStream(uri);
if (is == null) {
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败,Uri:" + uri.toString());
- return null;
+ return false;
}
// 2. 初始化选图临时文件输出流(Java7 手动创建流,不依赖try-with-resources)
@@ -294,7 +295,8 @@ public class BackgroundSourceUtils {
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
-
+ saveSettings();
+
// 6. 解析成功日志(打印文件信息,便于问题排查)
LogUtils.d(TAG, "【选图解析】Uri解析成功!");
LogUtils.d(TAG, "→ 原Uri:" + uri.toString());
@@ -302,14 +304,14 @@ public class BackgroundSourceUtils {
LogUtils.d(TAG, "→ 目标临时文件大小:" + mCropSourceFile.length() + " bytes");
LogUtils.d(TAG, "→ 目标剪裁临时文件:" + mCropResultFile.getAbsolutePath());
LogUtils.d(TAG, "→ 目标剪裁临时文件大小:" + mCropResultFile.length() + " bytes");
- return previewBackgroundBean;
+ return true;
} catch (Exception e) {
// 捕获所有异常(IO异常/空指针等),避免崩溃
LogUtils.e(TAG, "【选图解析】流复制异常:" + e.getMessage(), e);
// 异常时清理无效文件,防止残留
clearCropTempFiles();
- return null;
+ return false;
} finally {
// 7. 手动关闭流资源(Java7 标准写法,避免内存泄漏)
@@ -340,10 +342,7 @@ public class BackgroundSourceUtils {
currentBackgroundBean = new BackgroundBean(); // 正式Bean独立实例初始化
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean);
LogUtils.d(TAG, "【配置管理】正式背景Bean不存在,创建独立实例并保存到JSON");
- } else {
- // 修复:加载旧配置时,若压缩图路径不在BackgroundCrops,自动迁移路径(兼容历史数据)
- migrateCompressPathToNewDir(currentBackgroundBean, true);
- }
+ }
// 2. 加载预览Bean(独立实例:从previewBackgroundBean.json加载,不存在则新建,与正式Bean完全分离)
previewBackgroundBean = BackgroundBean.loadBeanFromFile(previewBackgroundBeanFile.getAbsolutePath(), BackgroundBean.class);
@@ -351,11 +350,7 @@ public class BackgroundSourceUtils {
previewBackgroundBean = new BackgroundBean(); // 预览Bean独立实例初始化
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean);
LogUtils.d(TAG, "【配置管理】预览背景Bean不存在,创建独立实例并保存到JSON");
- } else {
- // 修复:加载旧配置时,若压缩图路径不在BackgroundCrops,自动迁移路径(兼容历史数据)
- migrateCompressPathToNewDir(previewBackgroundBean, false);
}
- LogUtils.d(TAG, "【配置管理】两份Bean实例初始化完成:正式Bean=" + currentBackgroundBean.hashCode() + ",预览Bean=" + previewBackgroundBean.hashCode() + "(hash不同证明实例独立)");
}
// ------------------------------ 对外提供的核心方法(路径已适配新目录)------------------------------
@@ -547,6 +542,7 @@ public class BackgroundSourceUtils {
* 核心:深拷贝后,修改预览Bean不会影响正式Bean,两份实例完全独立,压缩图路径统一指向BackgroundCrops
*/
public void setCurrentSourceToPreview() {
+ LogUtils.d(TAG, "正在初始化预览数据,setCurrentSourceToPreview()");
// 深拷贝第一步:新建预览Bean独立实例(彻底脱离正式Bean的引用)
previewBackgroundBean = new BackgroundBean();
// 深拷贝第二步:逐字段拷贝正式Bean的所有值(压缩图路径同步指向BackgroundCrops)
@@ -561,8 +557,7 @@ public class BackgroundSourceUtils {
previewBackgroundBean.setBackgroundHeight(currentBackgroundBean.getBackgroundHeight());
previewBackgroundBean.setPixelColor(currentBackgroundBean.getPixelColor());
- saveSettings(); // 分别保存:正式Bean→currentJSON,预览Bean→previewJSON(两份独立)
- LogUtils.d(TAG, "【配置管理】正式背景深拷贝到预览Bean:两份实例独立,压缩图统一存储到BackgroundCrops");
+ saveSettings();
}
/**
diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java
index 1f28a8c9..d1227b99 100644
--- a/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java
+++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/utils/ImageCropUtils.java
@@ -1,126 +1,169 @@
package cc.winboll.studio.powerbell.utils;
import android.app.Activity;
-import android.content.ComponentName;
import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
-import android.provider.MediaStore;
-import android.util.Log;
-import android.widget.Toast;
import androidx.core.content.FileProvider;
import cc.winboll.studio.libappbase.LogUtils;
-import cc.winboll.studio.libappbase.ToastUtils;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.List;
import cc.winboll.studio.powerbell.model.BackgroundBean;
+import com.yalantis.ucrop.UCrop;
+import com.yalantis.ucrop.UCropActivity;
+import java.io.File;
+import cc.winboll.studio.powerbell.R;
/**
- * @Author ZhanGSKen&豆包大模型
- * @Date 2025/12/04 06:45
- * @Describe 图片裁剪工具类(仅调用系统裁剪,传入比例/自由裁剪参数)
+ * 图片裁剪工具类(集成uCrop,脱离系统依赖)
*/
public class ImageCropUtils {
public static final String TAG = "ImageCropUtils";
- // FileProvider授权(与清单文件一致)
-
-
-
+ // FileProvider 授权(与项目一致)
+ private static final String FILE_PROVIDER_SUFFIX = ".fileprovider";
-//
-// /**
-// * 保存剪裁后的Bitmap(优化版)
-// */
-// private void saveCropBitmap(Bitmap bitmap) {
-// if (bitmap == null) {
-// ToastUtils.show("剪裁图片为空");
-// 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 backgroundDir = new File(mBackgroundPictureUtils.getBackgroundDir());
-// if (!backgroundDir.exists()) {
-// if (!backgroundDir.mkdirs()) {
-// ToastUtils.show("无法创建保存目录");
-// if (scaledBitmap != bitmap) scaledBitmap.recycle();
-// return;
-// }
-// }
-//
-// File saveFile = new File(backgroundDir, getBackgroundFileName());
-//
-// // 优化:检查文件是否可写
-// if (saveFile.exists() && !saveFile.canWrite()) {
-// if (!saveFile.delete()) {
-// ToastUtils.show("无法删除旧文件");
-// if (scaledBitmap != bitmap) scaledBitmap.recycle();
-// return;
-// }
-// }
-//
-// FileOutputStream fos = null;
-// try {
-// fos = new FileOutputStream(saveFile);
-// boolean success = scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 80, fos);
-// fos.flush();
-// if (success) {
-// ToastUtils.show("保存成功");
-// // 更新数据
-// mBackgroundPictureUtils.getBackgroundPictureBean().setIsUseBackgroundFile(true);
-// updatePreviewBackground();
-// } else {
-// ToastUtils.show("图片压缩保存失败");
-// }
-// } catch (FileNotFoundException e) {
-// LogUtils.e(TAG, "文件未找到" + e);
-// ToastUtils.show("保存失败:文件路径错误");
-// } catch (IOException e) {
-// LogUtils.e(TAG, "写入异常" + e);
-// ToastUtils.show("保存失败:磁盘可能已满或路径错误");
-// } finally {
-// if (fos != null) {
-// try {
-// fos.close();
-// } catch (IOException e) {
-// LogUtils.e(TAG, "流关闭异常" + e);
-// }
-// }
-// if (scaledBitmap != null && !scaledBitmap.isRecycled()) {
-// scaledBitmap.recycle();
-// }
-// }
-// }
-//
-// /**
-// * 缩放Bitmap
-// */
-// private Bitmap scaleBitmap(Bitmap original, float scale) {
-// if (original == null) {
-// return null;
-// }
-// int width = (int) (original.getWidth() * scale);
-// int height = (int) (original.getHeight() * scale);
-// return Bitmap.createScaledBitmap(original, width, height, true);
-// }
+ /**
+ * 启动uCrop裁剪(核心方法,替代系统裁剪)
+ * @param activity 上下文
+ * @param inputFile 输入图片文件
+ * @param outputFile 输出图片文件
+ * @param isFreeCrop 是否自由裁剪(true=自由,false=固定比例)
+ * @param requestCode 裁剪请求码
+ */
+ public static void startImageCrop(Activity activity,
+ File inputFile,
+ File outputFile,
+ int aspectX,
+ int aspectY,
+ boolean isFreeCrop,
+ int requestCode) {
+ // 校验输入参数
+ if (activity == null || activity.isFinishing()) {
+ LogUtils.e(TAG, "【裁剪异常】上下文Activity无效");
+ return;
+ }
+ if (inputFile == null || !inputFile.exists() || inputFile.length() <= 100) {
+ LogUtils.e(TAG, "【裁剪异常】输入文件无效");
+ showToast(activity, "无有效图片可裁剪");
+ return;
+ }
+ if (outputFile == null) {
+ LogUtils.e(TAG, "【裁剪异常】输出文件路径为空");
+ showToast(activity, "裁剪输出路径无效");
+ return;
+ }
+
+ // 生成输入/输出Uri(适配FileProvider)
+ Uri inputUri = getFileProviderUri(activity, inputFile);
+ Uri outputUri = Uri.fromFile(outputFile); // uCrop 支持直接用文件Uri(兼容低版本)
+
+ // 配置uCrop参数
+ UCrop uCrop = UCrop.of(inputUri, outputUri);
+ UCrop.Options options = new UCrop.Options();
+
+ // 裁剪模式配置(自由裁剪/固定比例)
+ if (isFreeCrop) {
+ // 自由裁剪:无固定比例,可随意调整
+ uCrop.withAspectRatio(0, 0);
+ options.setFreeStyleCropEnabled(true); // 开启自由裁剪
+ } else {
+ // 固定比例(默认1:1,可根据需求修改)
+ uCrop.withAspectRatio(aspectX, aspectY);
+ options.setFreeStyleCropEnabled(false);
+ }
+
+ // 裁剪配置(优化体验)
+ options.setCompressionFormat(android.graphics.Bitmap.CompressFormat.JPEG); // 输出格式
+ options.setCompressionQuality(100); // 图片质量
+ options.setHideBottomControls(true); // 隐藏底部控制栏(简化界面)
+ options.setToolbarTitle("图片裁剪"); // 工具栏标题
+ options.setToolbarColor(activity.getResources().getColor(R.color.colorPrimary)); // 工具栏颜色(适配项目主题)
+ options.setStatusBarColor(activity.getResources().getColor(R.color.colorPrimaryDark)); // 状态栏颜色
+
+ // 应用配置并启动裁剪
+ uCrop.withOptions(options);
+ // 启动uCrop裁剪Activity(替代系统裁剪)
+ uCrop.start(activity, requestCode);
+
+ LogUtils.d(TAG, "【uCrop启动】成功,输入Uri:" + inputUri + ",输出Uri:" + outputUri + ",请求码:" + requestCode);
+ }
+
+ /**
+ * 重载方法:适配BackgroundBean
+ */
+ public static void startImageCrop(Activity activity,
+ BackgroundBean cropBean,
+ int aspectX,
+ int aspectY,
+ boolean isFreeCrop,
+ int requestCode) {
+ File inputFile = new File(cropBean.getBackgroundFilePath());
+ File outputFile = new File(cropBean.getBackgroundScaledCompressFilePath());
+ startImageCrop(activity, inputFile, outputFile, aspectX, aspectY, isFreeCrop, requestCode);
+ }
+
+ /**
+ * 生成FileProvider Uri
+ */
+ private static Uri getFileProviderUri(Activity activity, File file) {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ String authority = activity.getPackageName() + FILE_PROVIDER_SUFFIX;
+ Uri uri = FileProvider.getUriForFile(activity, authority, file);
+ LogUtils.d(TAG, "【Uri生成】FileProvider Uri:" + uri);
+ return uri;
+ } else {
+ Uri uri = Uri.fromFile(file);
+ LogUtils.d(TAG, "【Uri生成】普通Uri:" + uri);
+ return uri;
+ }
+ } catch (Exception e) {
+ LogUtils.e(TAG, "【Uri生成】失败:" + e.getMessage());
+ return null;
+ }
+ }
+
+ /**
+ * 处理uCrop裁剪回调(在Activity的onActivityResult中调用)
+ * @param requestCode 请求码
+ * @param resultCode 结果码
+ * @param data 回调数据
+ * @return 裁剪成功返回输出文件路径,失败返回null
+ */
+ public static String handleCropResult(int requestCode, int resultCode, Intent data, int cropRequestCode) {
+ // 校验是否是uCrop的回调
+ if (requestCode == cropRequestCode) {
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ // 裁剪成功,获取输出Uri
+ Uri outputUri = UCrop.getOutput(data);
+ if (outputUri != null) {
+ String outputPath = outputUri.getPath();
+ LogUtils.d(TAG, "【uCrop回调】裁剪成功,输出路径:" + outputPath);
+ return outputPath;
+ }
+ } else if (resultCode == UCrop.RESULT_ERROR) {
+ // 裁剪失败,获取异常信息
+ Throwable error = UCrop.getError(data);
+ LogUtils.e(TAG, "【uCrop回调】裁剪失败:" + (error != null ? error.getMessage() : "未知错误"));
+ } else {
+ LogUtils.d(TAG, "【uCrop回调】裁剪被取消");
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 显示Toast
+ */
+ private static void showToast(Activity activity, String msg) {
+ if (activity != null && !activity.isFinishing()) {
+ android.widget.Toast.makeText(activity, msg, android.widget.Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ /**
+ * 暴露getFileProviderUri方法(供外部调用)
+ */
+ public static Uri getFileProviderUriPublic(Activity activity, File file) {
+ return getFileProviderUri(activity, file);
+ }
}
diff --git a/powerbell/src/main/res/layout/activity_mainunittest.xml b/powerbell/src/main/res/layout/activity_mainunittest.xml
index b8e7d012..583ef92b 100644
--- a/powerbell/src/main/res/layout/activity_mainunittest.xml
+++ b/powerbell/src/main/res/layout/activity_mainunittest.xml
@@ -5,11 +5,47 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
-
-
+ android:background="#FF0C6BBF">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/powerbell/src/main/res/layout/fragment_test_backgroundview.xml b/powerbell/src/main/res/layout/fragment_test_backgroundview.xml
deleted file mode 100644
index 9fb71aba..00000000
--- a/powerbell/src/main/res/layout/fragment_test_backgroundview.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-