完成示例图片控件的引用与存储数据存取功能。

This commit is contained in:
ZhanGSKen
2025-11-20 20:16:40 +08:00
parent bb98d6bb1b
commit 82518af2d6
10 changed files with 426 additions and 102 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Nov 20 03:22:58 GMT 2025
#Thu Nov 20 12:14:50 GMT 2025
stageCount=4
libraryProject=
baseVersion=15.11
publishVersion=15.11.3
buildCount=34
buildCount=51
baseBetaVersion=15.11.4

View File

@@ -16,6 +16,7 @@ import cc.winboll.studio.powerbell.R;
import cc.winboll.studio.powerbell.utils.ImageDownloader;
import java.io.FileInputStream;
import java.io.IOException;
import cc.winboll.studio.powerbell.views.BackgroundView;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
@@ -33,8 +34,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
private Button btnConfirm;
private Button btnPreview;
private EditText etURL;
//private WebView webViewPreview;
ImageView ivBackgroundPreview;
BackgroundView bvBackgroundPreview;
Context mContext;
// 按钮点击回调接口Java7 接口实现)
@@ -77,7 +77,7 @@ public class NetworkBackgroundDialog extends AlertDialog {
btnPreview = (Button) dialogView.findViewById(R.id.btn_preview);
etURL = (EditText) dialogView.findViewById(R.id.et_url);
//webViewPreview = (WebView) dialogView.findViewById(R.id.webview_preview);
ivBackgroundPreview = (ImageView) findViewById(R.id.iv_background_preview);
bvBackgroundPreview = (BackgroundView) findViewById(R.id.bv_background_preview);
// 设置按钮点击事件
setButtonClickListeners();
@@ -163,14 +163,14 @@ public class NetworkBackgroundDialog extends AlertDialog {
Drawable drawable = Drawable.createFromStream(fis, null);
// 设置背景
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
ivBackgroundPreview.setBackground(drawable);
bvBackgroundPreview.setBackground(drawable);
} else {
ivBackgroundPreview.setBackgroundDrawable(drawable);
bvBackgroundPreview.setBackgroundDrawable(drawable);
}
} catch (Exception e) {
e.printStackTrace();
//ivBackgroundPreview.setBackgroundResource(R.drawable.default_error); // 异常时显示默认图
ivBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher); // 异常时显示默认图
bvBackgroundPreview.setBackgroundResource(R.drawable.ic_launcher); // 异常时显示默认图
} finally {
// Java7 手动关闭流,避免资源泄漏
if (fis != null) {

View File

@@ -5,6 +5,7 @@ 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;
@@ -21,11 +22,16 @@ public class BackgroundViewTestFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
//super.onCreateView(inflater, container, savedInstanceState);
// 非调试状态就结束本线程
if (!GlobalApplication.isDebugging()) {
Thread.currentThread().destroy();
}
mainView = inflater.inflate(R.layout.fragment_test_backgroundview, container, false);
ToastUtils.show(String.format("%s onCreate", TAG));
return mainView;
}
}

View File

@@ -1,34 +0,0 @@
package cc.winboll.studio.powerbell.unittest;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:56
* @Describe BaseTestFragment
*/
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;
public class BaseTestFragment extends Fragment {
public static final String TAG = "BaseTestFragment";
View mainView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
// 非调试状态就结束本线程
if (!GlobalApplication.isDebugging()) {
Thread.currentThread().destroy();
}
ToastUtils.show(String.format("%s onCreate", TAG));
return null;
}
}

View File

@@ -1,50 +1,240 @@
package cc.winboll.studio.powerbell.views;
import android.content.Context;
import android.graphics.Bitmap;
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.LinearLayout;
import android.widget.RelativeLayout;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/11/19 18:01
* @Describe 背景图片视图控件
* @Describe 背景图片视图控件(加载外置存储 Background/current.jpg
*/
public class BackgroundView extends LinearLayout {
public class BackgroundView extends RelativeLayout {
public static final String TAG = "BackgroundView";
Context mContext;
View mMianView;
public BackgroundView(Context context) {
super(context);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.mContext = context;
initView();
}
void initView() {
this.mMianView = inflate(this.mContext, R.layout.view_background, null);
addView(this.mMianView);
}
Context mContext;
View mMianView;
// 外置存储背景图片路径:/storage/emulated/0/Android/data/应用包名/files/Background/current.jpg
private static String BACKGROUND_IMAGE_FOLDER = "Background";
private static String BACKGROUND_IMAGE_FILENAME = "current.data";
private static String backgroundSourceFilePath;
public BackgroundView(Context context) {
super(context);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
this.mContext = context;
initView();
}
void initView() {
// 1. 初始化外置存储背景图片路径(应用私有外置存储,无需动态权限)
initBackgroundImagePath();
// 2. 加载并设置背景图片
loadAndSetBackground();
}
/**
* 初始化外置存储背景图片路径
* 路径:/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);
}
/**
* 拷贝图片文件到背景资源目录
* @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() {
// 校验路径是否有效
if (backgroundSourceFilePath == null) {
setDefaultBackground();
return;
}
File backgroundFile = new File(backgroundSourceFilePath);
// 校验文件是否存在
if (!backgroundFile.exists() || !backgroundFile.isFile()) {
LogUtils.e(TAG, "背景图片不存在:" + backgroundSourceFilePath);
setDefaultBackground();
return;
}
// 3. 压缩加载 Bitmap避免 OOM
Bitmap bitmap = decodeBitmapWithCompress(backgroundFile, 1080, 1920); // 最大宽高限制
if (bitmap == null) {
LogUtils.e(TAG, "图片加载失败,无法解析为 Bitmap");
setDefaultBackground();
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);
} else {
this.setBackgroundDrawable(backgroundDrawable);
}
LogUtils.d(TAG, "背景图片加载成功");
}
/**
* 带压缩的 Bitmap 解码(避免大图导致 OOM
* @param file 图片文件
* @param maxWidth 最大宽度限制
* @param maxHeight 最大高度限制
* @return 压缩后的 Bitmapnull 表示失败)
*/
private Bitmap decodeBitmapWithCompress(File file, int maxWidth, int maxHeight) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
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
}
// 正式加载图片并压缩
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; // 降低像素格式,减少内存占用
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
} catch (Exception e) {
LogUtils.e(TAG, "图片压缩加载失败:" + e.getMessage());
return null;
}
}
/**
* 设置默认背景(图片加载失败时兜底)
*/
private void setDefaultBackground() {
// 可替换为项目自定义的默认背景图
this.setBackgroundResource(R.drawable.default_background);
LogUtils.d(TAG, "已设置默认背景");
}
}

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF009DCB"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

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

View File

@@ -46,11 +46,10 @@
android:layout_marginTop="12dp"
android:layout_gravity="center_vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:id="@+id/iv_background_preview"/>
<cc.winboll.studio.powerbell.views.BackgroundView
android:layout_width="100dp"
android:layout_height="100dp"
android:id="@+id/bv_background_preview"/>
</LinearLayout>

View File

@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<cc.winboll.studio.powerbell.views.BackgroundView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFA08DF4">
android:background="#FF7381FF">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="IT"/>
</LinearLayout>
</cc.winboll.studio.powerbell.views.BackgroundView>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF7DE0D7"
android:id="@+id/viewbackground_container">
</LinearLayout>