diff --git a/powerbell/build.properties b/powerbell/build.properties index 356beba8..46e17655 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Wed Dec 03 22:24:41 GMT 2025 +#Wed Dec 03 22:37:16 GMT 2025 stageCount=13 libraryProject= baseVersion=15.11 publishVersion=15.11.12 -buildCount=188 +buildCount=191 baseBetaVersion=15.11.13 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 index 81459531..7504073b 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/unittest/BackgroundViewTestFragment.java @@ -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"); } } diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java index 07fc3ff0..e7717bf9 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/BackgroundView.java @@ -3,195 +3,252 @@ package cc.winboll.studio.powerbell.views; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.text.TextUtils; import android.util.AttributeSet; -import android.view.View; -import android.util.Log; - -import java.io.File; +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 java.io.File; /** - * 图片背景设置视图(完全独立类,不依赖BackgroundSettingsActivity) - * 仅通过文件路径自主解析比例,不接收任何外部比例参数 + * 基于Java7的BackgroundView(LinearLayout+ImageView,保持原图比例居中平铺) + * 核心:ImageView保持原图比例,在LinearLayout中居中平铺,无拉伸、无裁剪 */ -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 class BackgroundView extends RelativeLayout { + 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(); } - /** - * 初始化视图(Java7语法,无Lambda) - */ + // ====================================== 初始化 ====================================== private void initView() { - Log.d(TAG, "initView: 初始化BackgroundView"); - mPaint = new Paint(); - mPaint.setAntiAlias(true); // 抗锯齿 - mPaint.setFilterBitmap(true); // 过滤 bitmap,使绘制更清晰 - mDrawRect = new RectF(); - mMatrix = new Matrix(); + 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); + // 配置ImageView:wrap_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()); + } + } + + // ====================================== 对外方法 ====================================== /** - * 加载背景图片(仅接收BackgroundBean,仅读取文件路径,自主解析比例) - * @param bean 背景图片信息(含文件路径,无比例信息) + * 加载图片(保持原图比例,在LinearLayout中居中平铺) + * @param imagePath 图片绝对路径 */ - 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(); - } - - /** - * 解析图片文件(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为空,跳过绘制"); + public void loadImage(String imagePath) { + LogUtils.d(TAG, "=== loadImage 启动,路径:" + imagePath + " ==="); + if (TextUtils.isEmpty(imagePath)) { + setDefaultTransparentBackground(); return; } - // 计算缩放比例(自主计算,基于视图尺寸和图片尺寸,无外部依赖) - float viewWidth = mDrawRect.width(); - float viewHeight = mDrawRect.height(); - float bitmapWidth = mBackgroundBitmap.getWidth(); - float bitmapHeight = mBackgroundBitmap.getHeight(); + File imageFile = new File(imagePath); + if (!imageFile.exists() || !imageFile.isFile()) { + LogUtils.e(TAG, "图片文件无效"); + setDefaultTransparentBackground(); + return; + } - // 计算缩放比例(保持比例,填满视图,裁剪多余部分) - float scaleX = viewWidth / bitmapWidth; - float scaleY = viewHeight / bitmapHeight; - float scale = Math.max(scaleX, scaleY); // 取较大缩放比,确保填满视图 + // 计算原图比例 + if (!calculateImageAspectRatio(imageFile)) { + setDefaultTransparentBackground(); + return; + } - // 计算偏移量(水平/垂直居中) - float translateX = (viewWidth - bitmapWidth * scale) / 2; - float translateY = (viewHeight - bitmapHeight * scale) / 2; + // 压缩加载Bitmap + Bitmap bitmap = decodeBitmapWithCompress(imageFile, 1080, 1920); + if (bitmap == null) { + setDefaultTransparentBackground(); + return; + } - // 设置矩阵(Java7 手动设置,不使用Lambda) - mMatrix.reset(); - mMatrix.postScale(scale, scale); - mMatrix.postTranslate(translateX, translateY); + // 设置图片 + mIvBackground.setImageDrawable(new BitmapDrawable(mContext.getResources(), bitmap)); + adjustImageViewSize(); // 调整尺寸 + LogUtils.d(TAG, "=== loadImage 完成 ==="); + } - // 绘制Bitmap - canvas.drawBitmap(mBackgroundBitmap, mMatrix, mPaint); - Log.d(TAG, "onDraw: 背景图片绘制完成,缩放比:" + scale + ",偏移量:(" + translateX + "," + translateY + ")"); + // ====================================== 内部工具方法 ====================================== + 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; + } + } + + 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()); + return null; + } } /** - * 回收资源(Java7 手动回收,避免内存泄漏) + * 调整ImageView尺寸(保持原图比例,在LinearLayout中居中平铺) */ - public void releaseResource() { - Log.d(TAG, "releaseResource: 回收BackgroundView资源"); - if (mBackgroundBitmap != null && !mBackgroundBitmap.isRecycled()) { - mBackgroundBitmap.recycle(); - mBackgroundBitmap = null; + private void adjustImageViewSize() { + LogUtils.d(TAG, "=== adjustImageViewSize 启动 ==="); + if (mLlContainer == null || mIvBackground == null) { + LogUtils.e(TAG, "控件为空"); + return; } - mPaint = null; - mDrawRect = null; - mMatrix = 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(); // 尺寸变化时重新调整 } } -