设置窗口源码合并完成

This commit is contained in:
2025-12-04 06:27:37 +08:00
parent c930308425
commit 75415956eb
6 changed files with 1188 additions and 601 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Dec 03 21:14:37 GMT 2025
#Wed Dec 03 22:24:41 GMT 2025
stageCount=13
libraryProject=
baseVersion=15.11
publishVersion=15.11.12
buildCount=180
buildCount=188
baseBetaVersion=15.11.13

View File

@@ -60,6 +60,6 @@ public class BackgroundViewTestFragment extends Fragment {
void loadBackground() {
mBackgroundView.loadImage("/storage/emulated/0/Pictures/Gallery/owner/素材/1626915857361.png");
//mBackgroundView.loadImage("/storage/emulated/0/Pictures/Gallery/owner/素材/1626915857361.png");
}
}

View File

@@ -19,6 +19,8 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.os.Build;
import androidx.core.content.FileProvider;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
@@ -216,7 +218,32 @@ public class BackgroundSourceUtils {
clearCropTempFiles();
LogUtils.d(TAG, "【文件初始化】完成。");
}
// 【核心实现】定义 getFileProviderUri 方法:将 File 转为 ContentUri适配 FileProvider
public Uri getFileProviderUri(File file) {
Log.d("BackgroundSourceUtils", "getFileProviderUri: 生成FileProvider Uri文件路径" + file.getAbsolutePath());
Uri contentUri = null;
try {
// 适配 Android 7.0+:使用 FileProvider 生成 ContentUri避免 FileUriExposedException
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
contentUri = FileProvider.getUriForFile(
mContext,
FILE_PROVIDER_AUTHORITY, // 与清单文件中一致
file
);
Log.d("BackgroundSourceUtils", "getFileProviderUri: 7.0+ 生成ContentUri" + contentUri.toString());
} else {
// 适配 Android 7.0 以下:直接使用 File.toURI()(兼容旧版本)
contentUri = Uri.fromFile(file);
Log.d("BackgroundSourceUtils", "getFileProviderUri: 7.0以下 生成FileUri" + contentUri.toString());
}
} catch (IllegalArgumentException e) {
// 捕获异常(如文件路径无效、授权不匹配等)
Log.e("BackgroundSourceUtils", "getFileProviderUri: 生成Uri失败异常" + e.getMessage(), e);
contentUri = null;
}
return contentUri;
}
public boolean createCropFileProviderPath(Uri uri) {
InputStream is = null;

View File

@@ -3,252 +3,195 @@ 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.ColorDrawable;
import android.text.TextUtils;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.model.BackgroundBean;
import android.view.View;
import android.util.Log;
import java.io.File;
import cc.winboll.studio.powerbell.model.BackgroundBean;
/**
* 基于Java7的BackgroundViewLinearLayout+ImageView保持原图比例居中平铺
* 核心ImageView保持原图比例在LinearLayout中居中平铺无拉伸、无裁剪
* 图片背景设置视图完全独立类不依赖BackgroundSettingsActivity
* 仅通过文件路径自主解析比例,不接收任何外部比例参数
*/
public class BackgroundView extends RelativeLayout {
public class BackgroundView extends View {
private static final String TAG = "BackgroundView";
private Bitmap mBackgroundBitmap; // 背景图片Bitmap
private Paint mPaint; // 绘制画笔
private RectF mDrawRect; // 绘制区域
private Matrix mMatrix; // 缩放矩阵
public static final String TAG = "BackgroundView";
private Context mContext;
private LinearLayout mLlContainer; // 主容器LinearLayout
private ImageView mIvBackground; // 图片显示控件
private float mImageAspectRatio = 1.0f; // 原图宽高比(宽/高)
// ====================================== 构造器Java7兼容 ======================================
public BackgroundView(Context context) {
super(context);
LogUtils.d(TAG, "=== BackgroundView 构造器1 启动 ===");
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs) {
super(context, attrs);
LogUtils.d(TAG, "=== BackgroundView 构造器2 启动 ===");
this.mContext = context;
initView();
}
public BackgroundView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LogUtils.d(TAG, "=== BackgroundView 构造器3 启动 ===");
this.mContext = context;
initView();
}
// ====================================== 初始化 ======================================
private void initView() {
LogUtils.d(TAG, "=== initView 启动 ===");
// 1. 配置当前控件:全屏+透明
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
setBackgroundColor(0x00000000);
setBackground(new ColorDrawable(0x00000000));
// 2. 初始化主容器LinearLayout
initLinearLayout();
// 3. 初始化ImageView
initImageView();
// 4. 初始设置透明背景
setDefaultTransparentBackground();
LogUtils.d(TAG, "=== initView 完成 ===");
}
private void initLinearLayout() {
LogUtils.d(TAG, "=== initLinearLayout 启动 ===");
mLlContainer = new LinearLayout(mContext);
// 配置LinearLayout全屏+垂直方向+居中
LinearLayout.LayoutParams llParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
);
mLlContainer.setLayoutParams(llParams);
mLlContainer.setOrientation(LinearLayout.VERTICAL);
mLlContainer.setGravity(android.view.Gravity.CENTER); // 子View居中
mLlContainer.setBackgroundColor(0x00000000);
this.addView(mLlContainer);
LogUtils.d(TAG, "=== initLinearLayout 完成 ===");
}
private void initImageView() {
LogUtils.d(TAG, "=== initImageView 启动 ===");
mIvBackground = new ImageView(mContext);
// 配置ImageViewwrap_content+居中+透明背景
LinearLayout.LayoutParams ivParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
mIvBackground.setLayoutParams(ivParams);
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 保持比例+居中平铺
mIvBackground.setBackgroundColor(0x00000000);
mLlContainer.addView(mIvBackground);
LogUtils.d(TAG, "=== initImageView 完成 ===");
}
public void loadBackgroundBean(BackgroundBean bean) {
if(!bean.isUseBackgroundFile()) {
setDefaultTransparentBackground();
return;
}
if(bean.isUseBackgroundScaledCompressFile()) {
loadImage(bean.getBackgroundScaledCompressFilePath());
} else {
loadImage(bean.getBackgroundFilePath());
}
}
// ====================================== 对外方法 ======================================
/**
* 加载图片保持原图比例在LinearLayout中居中平铺
* @param imagePath 图片绝对路径
* 初始化视图Java7语法无Lambda
*/
public void loadImage(String imagePath) {
LogUtils.d(TAG, "=== loadImage 启动,路径:" + imagePath + " ===");
if (TextUtils.isEmpty(imagePath)) {
setDefaultTransparentBackground();
return;
}
File imageFile = new File(imagePath);
if (!imageFile.exists() || !imageFile.isFile()) {
LogUtils.e(TAG, "图片文件无效");
setDefaultTransparentBackground();
return;
}
// 计算原图比例
if (!calculateImageAspectRatio(imageFile)) {
setDefaultTransparentBackground();
return;
}
// 压缩加载Bitmap
Bitmap bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920);
if (bitmap == null) {
setDefaultTransparentBackground();
return;
}
// 设置图片
mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap));
adjustImageViewSize(); // 调整尺寸
LogUtils.d(TAG, "=== loadImage 完成 ===");
private void initView() {
Log.d(TAG, "initView: 初始化BackgroundView");
mPaint = new Paint();
mPaint.setAntiAlias(true); // 抗锯齿
mPaint.setFilterBitmap(true); // 过滤 bitmap使绘制更清晰
mDrawRect = new RectF();
mMatrix = new Matrix();
}
// ====================================== 内部工具方法 ======================================
private boolean calculateImageAspectRatio(File file) {
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
int width = options.outWidth;
int height = options.outHeight;
if (width <= 0 || height <= 0) {
LogUtils.e(TAG, "图片尺寸无效");
return false;
}
mImageAspectRatio = (float) width / height;
LogUtils.d(TAG, "原图比例:" + mImageAspectRatio);
return true;
} catch (Exception e) {
LogUtils.e(TAG, "计算比例失败:" + e.getMessage());
return false;
/**
* 加载背景图片仅接收BackgroundBean仅读取文件路径自主解析比例
* @param bean 背景图片信息(含文件路径,无比例信息)
*/
public void loadBackgroundBean(BackgroundBean bean) {
Log.d(TAG, "loadBackgroundBean: 开始加载背景图片,文件路径:" + bean.getBackgroundScaledCompressFilePath());
// 回收旧Bitmap避免内存泄漏Java7 手动回收)
if (mBackgroundBitmap != null && !mBackgroundBitmap.isRecycled()) {
mBackgroundBitmap.recycle();
mBackgroundBitmap = null;
Log.d(TAG, "loadBackgroundBean: 回收旧背景Bitmap");
}
// 读取文件路径,自主解析图片(完全独立,不依赖外部比例)
String imagePath = bean.getBackgroundScaledCompressFilePath();
if (imagePath != null && !imagePath.isEmpty()) {
File imageFile = new File(imagePath);
mBackgroundBitmap = decodeBitmapFromFile(imageFile);
Log.d(TAG, "loadBackgroundBean: 背景图片加载完成,是否成功:" + (mBackgroundBitmap != null));
} else {
Log.e(TAG, "loadBackgroundBean: 图片路径为空,加载失败");
}
// 刷新视图
invalidate();
}
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;
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());
/**
* 解析图片文件Java7语法采样率加载自主计算比例
* @param imageFile 图片文件来自BackgroundBean的路径
* @return 解析后的Bitmap失败返回null
*/
private Bitmap decodeBitmapFromFile(File imageFile) {
Log.d(TAG, "decodeBitmapFromFile: 解析图片文件,路径:" + imageFile.getAbsolutePath());
// 校验文件有效性
if (imageFile == null || !imageFile.exists() || !imageFile.isFile() || imageFile.length() <= 100) {
Log.e(TAG, "decodeBitmapFromFile: 图片文件无效(不存在/非文件/过小)");
return null;
}
// Java7 语法Bitmap采样率加载避免OOM
BitmapFactory.Options options = new BitmapFactory.Options();
// 第一步仅获取图片尺寸不加载完整Bitmap
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
// 第二步计算采样率最大尺寸限制为2048px适配所有机型
int maxSize = 2048;
int sampleSize = 1;
// Java7 普通while循环不使用增强for/ Lambda
while (options.outWidth / sampleSize > maxSize || options.outHeight / sampleSize > maxSize) {
sampleSize *= 2;
}
sampleSize = Math.min(sampleSize, 16); // 限制最大采样率,避免模糊
Log.d(TAG, "decodeBitmapFromFile: 图片原始尺寸:" + options.outWidth + "x" + options.outHeight + ",采样率:" + sampleSize);
// 第三步设置采样率加载完整Bitmap
options.inJustDecodeBounds = false;
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565; // 节省内存Java7 兼容配置)
options.inPurgeable = true; // 允许内存不足时回收
options.inInputShareable = true; // 配合inPurgeable使用
// 第四步解析文件捕获异常Java7 手动捕获)
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options);
if (bitmap == null || bitmap.isRecycled()) {
Log.e(TAG, "decodeBitmapFromFile: 解析失败Bitmap为空或已回收");
return null;
}
Log.d(TAG, "decodeBitmapFromFile: 解析成功Bitmap尺寸" + bitmap.getWidth() + "x" + bitmap.getHeight() + ",内存大小:" + bitmap.getByteCount() / 1024 + "KB");
} catch (OutOfMemoryError e) {
Log.e(TAG, "decodeBitmapFromFile: OOM异常图片过大" + e.getMessage());
return null;
} catch (Exception e) {
Log.e(TAG, "decodeBitmapFromFile: 解析异常:" + e.getMessage());
return null;
}
return bitmap;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 获取视图实际尺寸
int viewWidth = MeasureSpec.getSize(widthMeasureSpec);
int viewHeight = MeasureSpec.getSize(heightMeasureSpec);
Log.d(TAG, "onMeasure: 视图尺寸:" + viewWidth + "x" + viewHeight);
mDrawRect.set(0, 0, viewWidth, viewHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw: 绘制背景图片");
// 若Bitmap为空不绘制
if (mBackgroundBitmap == null || mBackgroundBitmap.isRecycled()) {
Log.w(TAG, "onDraw: 背景Bitmap为空跳过绘制");
return;
}
// 计算缩放比例(自主计算,基于视图尺寸和图片尺寸,无外部依赖)
float viewWidth = mDrawRect.width();
float viewHeight = mDrawRect.height();
float bitmapWidth = mBackgroundBitmap.getWidth();
float bitmapHeight = mBackgroundBitmap.getHeight();
// 计算缩放比例(保持比例,填满视图,裁剪多余部分)
float scaleX = viewWidth / bitmapWidth;
float scaleY = viewHeight / bitmapHeight;
float scale = Math.max(scaleX, scaleY); // 取较大缩放比,确保填满视图
// 计算偏移量(水平/垂直居中)
float translateX = (viewWidth - bitmapWidth * scale) / 2;
float translateY = (viewHeight - bitmapHeight * scale) / 2;
// 设置矩阵Java7 手动设置不使用Lambda
mMatrix.reset();
mMatrix.postScale(scale, scale);
mMatrix.postTranslate(translateX, translateY);
// 绘制Bitmap
canvas.drawBitmap(mBackgroundBitmap, mMatrix, mPaint);
Log.d(TAG, "onDraw: 背景图片绘制完成,缩放比:" + scale + ",偏移量:(" + translateX + "," + translateY + ")");
}
/**
* 调整ImageView尺寸保持原图比例在LinearLayout中居中平铺
* 回收资源Java7 手动回收,避免内存泄漏
*/
private void adjustImageViewSize() {
LogUtils.d(TAG, "=== adjustImageViewSize 启动 ===");
if (mLlContainer == null || mIvBackground == null) {
LogUtils.e(TAG, "控件为空");
return;
public void releaseResource() {
Log.d(TAG, "releaseResource: 回收BackgroundView资源");
if (mBackgroundBitmap != null && !mBackgroundBitmap.isRecycled()) {
mBackgroundBitmap.recycle();
mBackgroundBitmap = null;
}
// 获取LinearLayout尺寸
int llWidth = mLlContainer.getWidth();
int llHeight = mLlContainer.getHeight();
if (llWidth == 0 || llHeight == 0) {
postDelayed(new Runnable() {
@Override
public void run() {
adjustImageViewSize();
}
}, 10);
return;
}
// 计算ImageView尺寸保持比例不超出LinearLayout
int ivWidth, ivHeight;
if (mImageAspectRatio >= 1.0f) {
ivWidth = Math.min((int) (llHeight * mImageAspectRatio), llWidth);
ivHeight = (int) (ivWidth / mImageAspectRatio);
} else {
ivHeight = Math.min((int) (llWidth / mImageAspectRatio), llHeight);
ivWidth = (int) (ivHeight * mImageAspectRatio);
}
// 应用尺寸
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mIvBackground.getLayoutParams();
params.width = ivWidth;
params.height = ivHeight;
mIvBackground.setLayoutParams(params);
mIvBackground.setScaleType(ScaleType.FIT_CENTER); // 确保居中平铺
LogUtils.d(TAG, "ImageView尺寸" + ivWidth + "x" + ivHeight);
LogUtils.d(TAG, "=== adjustImageViewSize 完成 ===");
}
private void setDefaultTransparentBackground() {
mIvBackground.setImageBitmap(null);
mIvBackground.setBackgroundColor(0x00000000);
mImageAspectRatio = 1.0f;
}
// ====================================== 重写方法 ======================================
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
adjustImageViewSize(); // 尺寸变化时重新调整
mPaint = null;
mDrawRect = null;
mMatrix = null;
}
}

View File

@@ -32,7 +32,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF3243E2"
android:id="@+id/activitybackgroundpictureBackgroundView1">
android:id="@+id/background_view">
</cc.winboll.studio.powerbell.views.BackgroundView>