package com.xw.common.custom import android.content.Context import android.graphics.* import android.graphics.drawable.Drawable import android.util.AttributeSet import android.util.Log import android.view.View import androidx.core.content.ContextCompat import androidx.core.graphics.withSave import com.xw.common.R import androidx.core.graphics.toColorInt import com.xw.common.utils.BindUtils.getResColor /** * 电池自定义View * 外壳完全由资源文件提供(默认 R.drawable.svg_battery),代码只绘制电量填充和文字 */ class BatteryView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : View(context, attrs, defStyleAttr) { companion object { private const val DEFAULT_HIGH_THRESHOLD = 80 private const val DEFAULT_MIDDLE_THRESHOLD = 15 private const val DEFAULT_LOW_THRESHOLD = 5 private const val MAXIMUM_BATTERY_LIFE = 700 // 车辆最大续航 // 默认电池外壳资源ID private val DEFAULT_FRAME_RES_ID = R.drawable.svg_battery fun getBatteryColor(batteryLevel: Int) = when { batteryLevel > DEFAULT_HIGH_THRESHOLD -> getResColor(R.color.green_6) batteryLevel > DEFAULT_MIDDLE_THRESHOLD -> getResColor(R.color.orange_5) batteryLevel > DEFAULT_LOW_THRESHOLD -> getResColor(R.color.red_4) else -> getResColor(R.color.gray_6) } fun getBatteryColorByBatteryLife(batteryLife: Int) = getBatteryColor((batteryLife * 100f / MAXIMUM_BATTERY_LIFE).toInt()) } // 电量百分比 (0-100) var batteryLevel: Int = 100 set(value) { field = value.coerceIn(0, 100) invalidate() } // 是否显示百分比文字 var showPercentage: Boolean = true set(value) { field = value invalidate() } // 电池外壳Drawable private var batteryFrameDrawable: Drawable? = null // 画笔 private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG) // 填充区域参数(基于外壳内边距) private var fillLeft = 0f private var fillTop = 0f private var fillRight = 0f private var fillBottom = 0f init { // 从XML读取属性 context.theme.obtainStyledAttributes( attrs, R.styleable.BatteryView, defStyleAttr, 0 ).apply { try { batteryLevel = getInteger(R.styleable.BatteryView_batteryLevel, 100) showPercentage = getBoolean(R.styleable.BatteryView_showPercentage, true) // 读取外壳资源,未指定则使用默认 val frameResId = getResourceId(R.styleable.BatteryView_batteryFrame, DEFAULT_FRAME_RES_ID) setBatteryFrameResource(frameResId) } finally { recycle() } } textPaint.apply { textAlign = Paint.Align.CENTER isFakeBoldText = true } } /** * 设置电池外壳资源 */ fun setBatteryFrameResource(resId: Int) { batteryFrameDrawable = ContextCompat.getDrawable(context, resId) calculateFillArea() invalidate() } /** * 计算电量填充区域(基于外壳内边距) * 假设外壳图片有透明内区域用于显示电量 */ private fun calculateFillArea() { val drawable = batteryFrameDrawable ?: return // 填充区域内边距比例(相对于View尺寸) // 这些值可以根据实际svg_battery资源调整 val paddingLeftRatio = 0.08f val paddingTopRatio = 0.15f val paddingRightRatio = 0.20f // 右侧留出正极空间 val paddingBottomRatio = 0.15f fillLeft = width * paddingLeftRatio fillTop = height * paddingTopRatio fillRight = width * (1 - paddingRightRatio) fillBottom = height * (1 - paddingBottomRatio) } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) calculateFillArea() } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) if (width == 0 || height == 0) return canvas.withSave { // 1. 绘制电池外壳(资源图片) drawBatteryFrame(canvas) // 2. 绘制电量填充 drawBatteryFill(canvas) // 3. 绘制百分比文字 if (showPercentage) { drawPercentageText(canvas) } } } /** * 绘制电池外壳(由资源提供) */ private fun drawBatteryFrame(canvas: Canvas) { val drawable = batteryFrameDrawable ?: return drawable.setBounds(0, 0, width, height) drawable.draw(canvas) } /** * 绘制电量填充 */ private fun drawBatteryFill(canvas: Canvas) { if (batteryLevel <= 0) return val availableWidth = fillRight - fillLeft val fillWidth = availableWidth * batteryLevel / 100 val currentFillRight = fillLeft + fillWidth val fillColor = getBatteryColor(batteryLevel) // 绘制填充 fillPaint.apply { color = fillColor style = Paint.Style.FILL } // 圆角半径(根据高度自适应) val cornerRadius = (fillBottom - fillTop) / 3 canvas.drawRoundRect( fillLeft, fillTop, currentFillRight, fillBottom, cornerRadius, cornerRadius, fillPaint ) // 添加高光效果 val gradient = LinearGradient( fillLeft, fillTop, fillLeft, fillBottom, Color.argb(60, 255, 255, 255), Color.argb(0, 255, 255, 255), Shader.TileMode.CLAMP ) fillPaint.shader = gradient canvas.drawRoundRect( fillLeft, fillTop, currentFillRight, fillBottom, cornerRadius, cornerRadius, fillPaint ) fillPaint.shader = null } /** * 绘制百分比文字 */ private fun drawPercentageText(canvas: Canvas) { val centerX = (fillLeft + fillRight) / 2 val centerY = (fillTop + fillBottom) / 2 val bgColor = getBatteryColor(batteryLevel) textPaint.color = if (isLightColor(bgColor)) Color.BLACK else Color.WHITE // 文字大小自适应 val maxTextSize = (fillBottom - fillTop) * 0.7f textPaint.textSize = if (batteryLevel == 100) maxTextSize * 0.8f else maxTextSize // 阴影 textPaint.setShadowLayer(2f, 1f, 1f, Color.argb(100, 0, 0, 0)) canvas.drawText( "$batteryLevel%", centerX, centerY + textPaint.textSize * 0.35f, textPaint ) textPaint.clearShadowLayer() } private fun isLightColor(color: Int): Boolean { val brightness = (Color.red(color) * 299 + Color.green(color) * 587 + Color.blue(color) * 114) / 1000 return brightness > 128 } // 便捷方法 fun setLevel(level: Int) { batteryLevel = level } // 通过续航去设置电量 fun setBatteryLife(batteryLife: Int) { setLevel((batteryLife * 100f / MAXIMUM_BATTERY_LIFE).toInt()) } }