@@ -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.Image View ;
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的BackgroundView( LinearLayout+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 ) ;
// 配置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 ( ) ) ;
}
}
// ====================================== 对外方法 ======================================
/**
* 加载图片( 保持原图比例, 在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 adjustImageViewSiz e( ) {
LogUtils . d ( TAG , " === adjustImageViewSize 启动 === " ) ;
if ( mLlContainer = = null | | mIv Background = = null ) {
LogUtils . e ( TAG , " 控件为空 " ) ;
return ;
public void releaseResourc e( ) {
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 ;
}
}