添加图片设置预览功能与一些调试入口。

This commit is contained in:
ZhanGSKen
2025-11-21 18:24:22 +08:00
parent ada29fb2b4
commit 51793077bd
6 changed files with 300 additions and 148 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Fri Nov 21 14:24:01 HKT 2025
#Fri Nov 21 10:22:55 GMT 2025
stageCount=7
libraryProject=
baseVersion=15.11
publishVersion=15.11.6
buildCount=0
buildCount=9
baseBetaVersion=15.11.7

View File

@@ -45,6 +45,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
Context mContext;
// 主线程 Handler用于接收子线程消息并更新 UI
private Handler mUiHandler;
String previewFilePath;
// 按钮点击回调接口Java7 接口实现)
public interface OnDialogClickListener {
@@ -160,6 +161,9 @@ public class NetworkBackgroundDialog extends AlertDialog {
@Override
public void onClick(View v) {
LogUtils.d("NetworkBackgroundDialog", "确认按钮点击");
// 确定预览背景资源
bvBackgroundPreview.saveToBackgroundSources(previewFilePath);
dismiss(); // 关闭对话框
if (listener != null) {
listener.onConfirm();
@@ -192,18 +196,15 @@ public class NetworkBackgroundDialog extends AlertDialog {
File imageFile = new File(filePath);
if (!imageFile.exists()) {
LogUtils.e(TAG, "图片文件不存在:" + filePath);
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
ToastUtils.show("Test");
//bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher);
return;
}
fis = new FileInputStream(imageFile);
Drawable drawable = Drawable.createFromStream(fis, null);
// 设置背景(主线程安全操作)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
bvBackgroundPreview.setBackground(drawable);
} else {
bvBackgroundPreview.setBackgroundDrawable(drawable);
}
// 预览背景
previewFilePath = filePath;
bvBackgroundPreview.previewBackgroundImage(previewFilePath);
LogUtils.d(TAG, "图片预览成功:" + filePath);
} catch (Exception e) {

View File

@@ -8,6 +8,9 @@ import androidx.fragment.app.Fragment;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.powerbell.R;
import android.widget.Button;
import cc.winboll.studio.powerbell.MainActivity;
import android.content.Intent;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -30,6 +33,14 @@ public class BackgroundViewTestFragment extends Fragment {
}
mainView = inflater.inflate(R.layout.fragment_test_backgroundview, container, false);
((Button)mainView.findViewById(R.id.btn_main_activity)).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
getActivity().startActivity(new Intent(getActivity(), MainActivity.class));
}
});
ToastUtils.show(String.format("%s onCreate", TAG));
return mainView;

View File

@@ -6,7 +6,7 @@ import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
@@ -17,20 +17,23 @@ import java.io.FileOutputStream;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:01
* @Describe 背景图片视图控件(加载外置存储 Background/current.jpg
* @Describe 背景图片视图控件(支持预览临时图片 + 外部刷新
*/
public class BackgroundView extends RelativeLayout {
public static final String TAG = "BackgroundView";
Context mContext;
View mMianView;
// 外置存储背景图片路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg
private ImageView ivBackground;
private static String BACKGROUND_IMAGE_FOLDER = "Background";
private static String BACKGROUND_IMAGE_FILENAME = "current.data";
private static String backgroundSourceFilePath;
private static String BACKGROUND_IMAGE_PREVIEW_FILENAME = "current_preview.data";
private static String backgroundSourceFilePath;
private float imageAspectRatio = 1.0f; // 默认 1:1
// 标记当前是否处于预览状态
private boolean isPreviewMode = false;
public BackgroundView(Context context) {
super(context);
this.mContext = context;
@@ -56,172 +59,288 @@ public class BackgroundView extends RelativeLayout {
}
void initView() {
// 1. 初始化外置存储背景图片路径(应用私有外置存储,无需动态权限)
initBackgroundImageView();
initBackgroundImagePath();
// 2. 加载并设置背景图片
loadAndSetBackground();
loadAndSetImageViewBackground();
}
private void initBackgroundImageView() {
ivBackground = new ImageView(mContext);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
ivBackground.setLayoutParams(layoutParams);
ivBackground.setScaleType(ImageView.ScaleType.FIT_CENTER);
this.addView(ivBackground);
}
/**
* 初始化外置存储背景图片路径
* 路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg
*/
private void initBackgroundImagePath() {
// 获取应用私有外置存储的 files 目录API 19+ 无需权限)
File externalFilesDir = mContext.getExternalFilesDir(null);
if (externalFilesDir == null) {
LogUtils.e(TAG, "外置存储不可用,无法初始化背景图片路径");
return;
}
// 拼接 Background 目录和 current.jpg 文件路径
File backgroundDir = new File(externalFilesDir, "Background");
if(!backgroundDir.exists()){
backgroundDir.mkdirs();
}
//BACKGROUND_IMAGE_PATH = new File(backgroundDir, "current.jpg").getAbsolutePath();
//BACKGROUND_IMAGE_PATH = new File(backgroundDir, "current.data").getAbsolutePath();
backgroundSourceFilePath = new File(backgroundDir, BACKGROUND_IMAGE_FILENAME).getAbsolutePath();
//LogUtils.d(TAG, "背景图片路径:" + BACKGROUND_IMAGE_PATH);
File backgroundDir = new File(externalFilesDir, BACKGROUND_IMAGE_FOLDER);
if (!backgroundDir.exists()) {
backgroundDir.mkdirs();
}
backgroundSourceFilePath = new File(backgroundDir, BACKGROUND_IMAGE_FILENAME).getAbsolutePath();
}
/**
* 拷贝图片文件到背景资源目录
* @param srcBackgroundPath 源文件路径(待拷贝的图片路径)
*/
public void saveToBackgroundSources(String srcBackgroundPath) {
// 1. 初始化目标路径(确保路径有效)
initBackgroundImagePath();
if (backgroundSourceFilePath == null) {
LogUtils.e(TAG, "目标路径初始化失败,无法保存背景图片");
return;
}
// 2. 校验源文件
File srcFile = new File(srcBackgroundPath);
if (!srcFile.exists() || !srcFile.isFile()) {
LogUtils.e(TAG, String.format("源文件不存在或不是文件:%s", srcBackgroundPath));
return;
}
// 3. 创建目标目录(若不存在)
File destFile = new File(backgroundSourceFilePath);
File destDir = destFile.getParentFile();
if (destDir != null && !destDir.exists()) {
boolean isDirCreated = destDir.mkdirs();
if (!isDirCreated) {
LogUtils.e(TAG, "目标目录创建失败:" + destDir.getAbsolutePath());
return;
}
}
// 4. 文件流拷贝Java 7 原生实现,兼容低版本)
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile); // 覆盖已有文件(如需询问用户可在此处添加逻辑)
// 缓冲区拷贝(提升效率,避免频繁 IO 操作)
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush(); // 强制刷新缓冲区,确保数据完整写入
LogUtils.d(TAG, String.format("文件拷贝成功:%s -> %s", srcBackgroundPath, backgroundSourceFilePath));
} catch (Exception e) {
// 捕获所有异常,避免崩溃
LogUtils.e(TAG, String.format("文件拷贝失败:%s", e.getMessage()), e);
// 拷贝失败时删除目标文件(避免生成损坏文件)
if (destFile.exists()) {
destFile.delete();
LogUtils.d(TAG, "已删除损坏的目标文件");
}
} finally {
// 5. 关闭流Java 7 手动关闭,避免资源泄漏)
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage());
}
}
if (fos != null) {
try {
fos.close();
} catch (Exception e) {
LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage());
}
}
}
}
/**
* 加载外置存储的 current.jpg 并设置为控件背景
* 支持:文件压缩、版本兼容、异常兜底
* 拷贝图片文件到背景资源目录(正式背景)
*/
private void loadAndSetBackground() {
// 校验路径是否有效
public void saveToBackgroundSources(String srcBackgroundPath) {
initBackgroundImagePath();
if (backgroundSourceFilePath == null) {
setDefaultBackground();
LogUtils.e(TAG, "目标路径初始化失败,无法保存背景图片");
return;
}
File srcFile = new File(srcBackgroundPath);
if (!srcFile.exists() || !srcFile.isFile()) {
LogUtils.e(TAG, String.format("源文件不存在或不是文件:%s", srcBackgroundPath));
return;
}
File destFile = new File(backgroundSourceFilePath);
File destDir = destFile.getParentFile();
if (destDir != null && !destDir.exists()) {
boolean isDirCreated = destDir.mkdirs();
if (!isDirCreated) {
LogUtils.e(TAG, "目标目录创建失败:" + destDir.getAbsolutePath());
return;
}
}
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(srcFile);
fos = new FileOutputStream(destFile);
byte[] buffer = new byte[4096];
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush();
LogUtils.d(TAG, String.format("文件拷贝成功:%s -> %s", srcBackgroundPath, backgroundSourceFilePath));
// 拷贝成功后,若处于预览模式则退出预览,加载正式背景
if (isPreviewMode) {
exitPreviewMode();
} else {
loadAndSetImageViewBackground();
}
} catch (Exception e) {
LogUtils.e(TAG, String.format("文件拷贝失败:%s", e.getMessage()), e);
if (destFile.exists()) {
destFile.delete();
LogUtils.d(TAG, "已删除损坏的目标文件");
}
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
LogUtils.e(TAG, "输入流关闭失败:" + e.getMessage());
}
}
if (fos != null) {
try {
fos.close();
} catch (Exception e) {
LogUtils.e(TAG, "输出流关闭失败:" + e.getMessage());
}
}
}
}
/**
* 【新增公共函数】预览临时图片(不修改正式背景文件)
* @param previewImagePath 临时预览图片的路径
*/
public void previewBackgroundImage(String previewImagePath) {
if (previewImagePath == null || previewImagePath.isEmpty()) {
LogUtils.e(TAG, "预览图片路径为空");
return;
}
File previewFile = new File(previewImagePath);
if (!previewFile.exists() || !previewFile.isFile()) {
LogUtils.e(TAG, "预览图片不存在或不是文件:" + previewImagePath);
return;
}
// 计算预览图片宽高比
if (!calculateImageAspectRatio(previewFile)) {
LogUtils.e(TAG, "预览图片尺寸无效,无法预览");
return;
}
// 压缩加载预览图片
Bitmap previewBitmap = decodeBitmapWithCompress(previewFile, 1080, 1920);
if (previewBitmap == null) {
LogUtils.e(TAG, "预览图片加载失败");
return;
}
// 设置预览图片到 ImageView
Drawable previewDrawable = new BitmapDrawable(mContext.getResources(), previewBitmap);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
ivBackground.setBackground(previewDrawable);
} else {
ivBackground.setBackgroundDrawable(previewDrawable);
}
// 调整 ImageView 尺寸以匹配预览图片宽高比
adjustImageViewSize();
isPreviewMode = true;
LogUtils.d(TAG, "进入预览模式,预览图片路径:" + previewImagePath);
}
/**
* 【新增公共函数】退出预览模式,恢复显示正式背景图片
*/
public void exitPreviewMode() {
if (isPreviewMode) {
loadAndSetImageViewBackground();
isPreviewMode = false;
LogUtils.d(TAG, "退出预览模式,恢复正式背景");
}
}
/**
* 公共函数:供外部类调用,重新加载正式背景图片(刷新显示)
*/
public void reloadBackgroundImage() {
LogUtils.d(TAG, "外部调用重新加载背景图片");
initBackgroundImagePath();
loadAndSetImageViewBackground();
// 若处于预览模式,退出预览
if (isPreviewMode) {
isPreviewMode = false;
}
}
/**
* 加载正式背景图片并设置到 ImageView
*/
private void loadAndSetImageViewBackground() {
if (backgroundSourceFilePath == null) {
setDefaultImageViewBackground();
return;
}
File backgroundFile = new File(backgroundSourceFilePath);
// 校验文件是否存在
if (!backgroundFile.exists() || !backgroundFile.isFile()) {
LogUtils.e(TAG, "背景图片不存在:" + backgroundSourceFilePath);
setDefaultBackground();
setDefaultImageViewBackground();
return;
}
// 3. 压缩加载 Bitmap避免 OOM
Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); // 最大宽高限制
if (!calculateImageAspectRatio(backgroundFile)) {
setDefaultImageViewBackground();
return;
}
Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920);
if (bitmap == null) {
LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap");
setDefaultBackground();
setDefaultImageViewBackground();
return;
}
// 4. 设置为控件背景(兼容 Android 低版本)
Drawable backgroundDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
this.setBackground(backgroundDrawable);
ivBackground.setBackground(backgroundDrawable);
} else {
this.setBackgroundDrawable(backgroundDrawable);
ivBackground.setBackgroundDrawable(backgroundDrawable);
}
LogUtils.d(TAG, "背景图片加载成功");
adjustImageViewSize();
LogUtils.d(TAG, "ImageView 背景加载成功,宽高比:" + imageAspectRatio);
}
/**
* 带压缩的 Bitmap 解码(避免大图导致 OOM
* @param file 图片文件
* @param maxWidth 最大宽度限制
* @param maxHeight 最大高度限制
* @return 压缩后的 Bitmapnull 表示失败)
* 计算图片宽高比(宽/高
*/
private boolean calculateImageAspectRatio(File file) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
int imageWidth = options.outWidth;
int imageHeight = options.outHeight;
if (imageWidth <= 0 || imageHeight <= 0) {
LogUtils.e(TAG, "图片尺寸无效:宽=" + imageWidth + ", 高=" + imageHeight);
return false;
}
imageAspectRatio = (float) imageWidth / imageHeight;
return true;
} catch (Exception e) {
LogUtils.e(TAG, "计算图片宽高比失败:" + e.getMessage());
return false;
}
}
/**
* 动态调整 ImageView 尺寸以匹配图片宽高比
*/
private void adjustImageViewSize() {
int parentWidth = getWidth();
int parentHeight = getHeight();
if (parentWidth == 0 || parentHeight == 0) {
post(new Runnable() {
@Override
public void run() {
adjustImageViewSize();
}
});
return;
}
int imageViewWidth, imageViewHeight;
if (imageAspectRatio >= 1.0f) { // 横图
imageViewWidth = Math.min(parentWidth, (int) (parentHeight * imageAspectRatio));
imageViewHeight = (int) (imageViewWidth / imageAspectRatio);
} else { // 竖图
imageViewHeight = Math.min(parentHeight, (int) (parentWidth / imageAspectRatio));
imageViewWidth = (int) (imageViewHeight * imageAspectRatio);
}
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivBackground.getLayoutParams();
layoutParams.width = imageViewWidth;
layoutParams.height = imageViewHeight;
ivBackground.setLayoutParams(layoutParams);
}
/**
* 带压缩的 Bitmap 解码(避免 OOM
*/
private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true; // 先获取图片尺寸,不加载到内存
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// 计算缩放比例
int scaleX = options.outWidth / maxWidth;
int scaleY = options.outHeight / maxHeight;
int inSampleSize = Math.max(scaleX, scaleY);
if (inSampleSize <= 0) {
inSampleSize = 1; // 最小缩放比例为 1
inSampleSize = 1;
}
// 正式加载图片并压缩
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; // 降低像素格式,减少内存占用
options.inPreferredConfig = Bitmap.Config.RGB_565;
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
} catch (Exception e) {
LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage());
@@ -232,9 +351,24 @@ public class BackgroundView extends RelativeLayout {
/**
* 设置默认背景(图片加载失败时兜底)
*/
private void setDefaultBackground() {
// 可替换为项目自定义的默认背景图
this.setBackgroundResource(R.drawable.default_background);
LogUtils.d(TAG, "已设置默认背景");
private void setDefaultImageViewBackground() {
ivBackground.setBackgroundResource(R.drawable.default_background);
imageAspectRatio = 1.0f;
adjustImageViewSize();
LogUtils.d(TAG, "已设置 ImageView 默认背景");
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
adjustImageViewSize();
}
/**
* 对外提供:判断当前是否处于预览模式
*/
public boolean isPreviewMode() {
return isPreviewMode;
}
}

View File

@@ -8,9 +8,8 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/activitymainunittestFrameLayout1"
android:layout_weight="1.0"/>
android:layout_height="match_parent"
android:id="@+id/activitymainunittestFrameLayout1"/>
</LinearLayout>

View File

@@ -7,10 +7,17 @@
android:layout_height="match_parent"
android:background="#FF7381FF">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IT"/>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Main"
android:id="@+id/btn_main_activity"/>
</HorizontalScrollView>
</cc.winboll.studio.powerbell.views.BackgroundView>