自定义电池电量显示控件 BatteryView 实现
在 Android 开发中,自定义视图(Custom View)是一种强大的工具,可以让你创建出独一无二的用户界面元素。本文将介绍如何实现一个自定义的电池电量显示控件 BatteryView,该控件可以显示电池的电量百分比,并支持充电状态的显示。
1. 项目背景
在许多应用中,显示设备的电池电量是一个常见的需求。为了提供更好的用户体验,我们希望这个电池电量显示控件能够直观地展示电池的电量状态,并且能够动态更新电量值。此外,当设备正在充电时,控件还需要能够显示充电状态。
2. 功能需求
- 显示电池电量百分比。
- 支持充电状态的显示。
- 支持自定义电池颜色、边框宽度、正极宽度等属性。
- 提供动画效果,使电量变化更加平滑。
3. 实现步骤
3.1 创建自定义视图类
首先,我们需要创建一个新的类 BatteryView,继承自 View 类。
package com.actionbar.demo.viewimport android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import com.actionbar.demo.R
3.2 定义属性
在 BatteryView 类中,定义一些默认的颜色值和尺寸相关的属性。
class BatteryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {// 默认颜色值private var borderColor = Color.BLACKprivate var powerColor = Color.GREENprivate var lowPowerColor = Color.REDprivate var mediumPowerColor = Color.YELLOWprivate var chargingColor = Color.GREENprivate var lightningColor = Color.WHITEprivate var textColor = Color.BLACK// 尺寸相关private var borderWidth = 4fvar power = 50get() {// 如果是在充电的话就返回100if (isCharging) {return 100}return field}set(value) {field = value.coerceIn(0, 100)invalidate()}var isCharging = falseset(value) {field = valueinvalidate()}var showPercentage = trueset(value) {field = valueinvalidate()}private var headWidth = 4f
3.3 初始化绘制工具
定义 Paint 对象和其他绘制工具。
// 绘制工具private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEstrokeWidth = borderWidth}private val batteryBodyRect = RectF()private val batteryHeadRect = RectF()private val powerRect = RectF()private val lightningPath = Path()
3.4 从 XML 属性读取自定义参数
在 init 方法中,从 XML 属性中读取自定义参数。
<declare-styleable name="BatteryView"><attr name="borderColor" format="color" /><attr name="powerColor" format="color" /><attr name="lowPowerColor" format="color" /><attr name="mediumPowerColor" format="color" /><attr name="chargingColor" format="color" /><attr name="lightningColor" format="color" /><attr name="batteryTextColor" format="color" /><attr name="borderWidth" format="dimension" /><attr name="headWidth" format="dimension" /><attr name="power" format="integer" /><attr name="charging" format="boolean" /><attr name="showPercentage" format="boolean" />
</declare-styleable>
init {// 从XML属性读取自定义参数context.theme.obtainStyledAttributes(attrs,R.styleable.BatteryView,0, 0).apply {try {borderColor = getColor(R.styleable.BatteryView_borderColor, Color.BLACK)powerColor = getColor(R.styleable.BatteryView_powerColor, Color.GREEN)lowPowerColor = getColor(R.styleable.BatteryView_lowPowerColor, Color.RED)mediumPowerColor = getColor(R.styleable.BatteryView_mediumPowerColor, Color.YELLOW)chargingColor = getColor(R.styleable.BatteryView_chargingColor, Color.YELLOW)lightningColor = getColor(R.styleable.BatteryView_lightningColor, Color.WHITE)textColor = getColor(R.styleable.BatteryView_batteryTextColor, Color.BLACK)borderWidth = getDimension(R.styleable.BatteryView_borderWidth, 4f)headWidth = getDimension(R.styleable.BatteryView_headWidth, 4f)power = getInt(R.styleable.BatteryView_power, 50)isCharging = getBoolean(R.styleable.BatteryView_charging, false)showPercentage = getBoolean(R.styleable.BatteryView_showPercentage, true)} finally {recycle()}}}
3.5 重写 onDraw 方法
在 onDraw 方法中,绘制电池主体、电池正极、电量部分以及充电状态或电量百分比。
override fun onDraw(canvas: Canvas) {super.onDraw(canvas)val width = width.toFloat()val height = height.toFloat()val cornerRadius = height / 10f// 1. 绘制电池主体 (圆角矩形)paint.color = borderColorpaint.style = Paint.Style.STROKEpaint.strokeWidth = borderWidthbatteryBodyRect.set(borderWidth / 2,borderWidth / 2,width - borderWidth * 1.5f - headWidth, // 留出正极位置height - borderWidth / 2)canvas.drawRoundRect(batteryBodyRect, cornerRadius, cornerRadius, paint)// 2. 绘制电池正极 (在右侧外部)val headWidth = headWidthval headHeight = height / 2fbatteryHeadRect.set(width - borderWidth - headWidth,height / 2 - headHeight / 2,width - borderWidth,height / 2 + headHeight / 2)paint.style = Paint.Style.FILLcanvas.drawRect(batteryHeadRect, paint)// 3. 绘制电量部分paint.style = Paint.Style.FILLpaint.color = getPowerColor()val powerPadding = borderWidth * 1.5fval powerWidth = (batteryBodyRect.width() - powerPadding * 2) * power / 100powerRect.set(batteryBodyRect.left + powerPadding,batteryBodyRect.top + powerPadding,batteryBodyRect.left + powerPadding + powerWidth,batteryBodyRect.bottom - powerPadding)canvas.drawRoundRect(powerRect, cornerRadius / 2, cornerRadius / 2, paint)// 4. 绘制充电状态或百分比if (isCharging) {drawLightning(canvas, batteryBodyRect.width(), batteryBodyRect.height())} else if (showPercentage) {drawPercentage(canvas, batteryBodyRect.width(), batteryBodyRect.height())}}
3.6 辅助方法
定义获取电量颜色、绘制充电闪电和绘制电量百分比的方法。
/*** 获取电量颜色*/private fun getPowerColor(): Int {return when {isCharging -> chargingColorpower < 20 -> lowPowerColorpower < 50 -> mediumPowerColorelse -> powerColor}}/*** 绘制充电闪电*/private fun drawLightning(canvas: Canvas, width: Float, height: Float) {val centerX = width / 2val centerY = height / 2val size = height / 3val offset = size * 0.05fval lightningPath1 = Path().apply {moveTo(centerX + offset, centerY - size / 2)lineTo(centerX + offset - size / 3, centerY + offset)lineTo(centerX + offset, centerY + offset)}val lightningPath2 = Path().apply {moveTo(centerX - offset, centerY - offset)lineTo(centerX - offset + size / 3, centerY - offset)lineTo(centerX - offset, centerY + size / 2)}lightningPath.reset()lightningPath.addPath(lightningPath1)lightningPath.addPath(lightningPath2)lightningPath.op(lightningPath2, Path.Op.UNION)paint.color = lightningColorpaint.style = Paint.Style.FILLcanvas.drawPath(lightningPath, paint)}/*** 绘制电量百分比*/private fun drawPercentage(canvas: Canvas, width: Float, height: Float) {val text = "$power%"val textSize = height / 2.5fpaint.color = if (power < 20) Color.WHITE else textColorpaint.textSize = textSizepaint.style = Paint.Style.FILLpaint.textAlign = Paint.Align.CENTER// 计算文本位置,避开正极val textX = width / 2val textY = height / 2 - (paint.descent() + paint.ascent()) / 2canvas.drawText(text, textX, textY, paint)}
3.7 动画效果
提供一个方法来实现电量变化的动画效果。
fun animatePower(targetPower: Int, duration: Long = 1000) {val animator = ValueAnimator.ofInt(power, targetPower)animator.duration = durationanimator.addUpdateListener { animation ->this.power = (animation.animatedValue as Int)}animator.start()}
}
4. 使用自定义视图
在布局文件中使用 BatteryView 控件。
<com.actionbar.demo.view.BatteryViewandroid:id="@+id/batteryView"android:layout_width="100dp"android:layout_height="50dp"app:borderColor="#000000"app:powerColor="#00FF00"app:lowPowerColor="#FF0000"app:mediumPowerColor="#FFFF00"app:chargingColor="#00FF00"app:lightningColor="#FFFFFF"app:batteryTextColor="#000000"app:borderWidth="4dp"app:headWidth="4dp"app:power="50"app:charging="false"app:showPercentage="true" />
在 Activity 或 Fragment 中使用 BatteryView 控件。
val batteryView: BatteryView = findViewById(R.id.batteryView)
batteryView.power = 75
batteryView.isCharging = true
batteryView.showPercentage = true// 动画效果
batteryView.animatePower(100, 2000)
5. 总结
通过本文,我们实现了一个自定义的电池电量显示控件 BatteryView,该控件可以显示电池的电量百分比,并支持充电状态的显示。这个控件不仅功能强大,而且易于使用,可以为你的 Android 应用增添更多的个性化元素。
希望本文对你有所帮助,如果你有任何问题或建议,欢迎在评论区留言交流!
完整代码
class BatteryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {// 默认颜色值private var borderColor = Color.BLACKprivate var powerColor = Color.GREENprivate var lowPowerColor = Color.REDprivate var mediumPowerColor = Color.YELLOWprivate var chargingColor = Color.GREENprivate var lightningColor = Color.WHITEprivate var textColor = Color.BLACK// 尺寸相关private var borderWidth = 4fvar power = 50get() {// 如果是在充电的话就返回100if (isCharging) {return 100}return field}set(value) {field = value.coerceIn(0, 100)invalidate()}var isCharging = falseset(value) {field = valueinvalidate()}var showPercentage = trueset(value) {field = valueinvalidate()}private var headWidth = 4f// 绘制工具private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {style = Paint.Style.STROKEstrokeWidth = borderWidth}private val batteryBodyRect = RectF()private val batteryHeadRect = RectF()private val powerRect = RectF()private val lightningPath = Path()init {// 从XML属性读取自定义参数context.theme.obtainStyledAttributes(attrs,R.styleable.BatteryView,0, 0).apply {try {borderColor = getColor(R.styleable.BatteryView_borderColor, Color.BLACK)powerColor = getColor(R.styleable.BatteryView_powerColor, Color.GREEN)lowPowerColor = getColor(R.styleable.BatteryView_lowPowerColor, Color.RED)mediumPowerColor = getColor(R.styleable.BatteryView_mediumPowerColor, Color.YELLOW)chargingColor = getColor(R.styleable.BatteryView_chargingColor, Color.YELLOW)lightningColor = getColor(R.styleable.BatteryView_lightningColor, Color.WHITE)textColor = getColor(R.styleable.BatteryView_batteryTextColor, Color.BLACK)borderWidth = getDimension(R.styleable.BatteryView_borderWidth, 4f)headWidth = getDimension(R.styleable.BatteryView_headWidth, 4f)power = getInt(R.styleable.BatteryView_power, 50)isCharging = getBoolean(R.styleable.BatteryView_charging, false)showPercentage = getBoolean(R.styleable.BatteryView_showPercentage, true)} finally {recycle()}}}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)val width = width.toFloat()val height = height.toFloat()val cornerRadius = height / 10f// 1. 绘制电池主体 (圆角矩形)paint.color = borderColorpaint.style = Paint.Style.STROKEpaint.strokeWidth = borderWidthbatteryBodyRect.set(borderWidth / 2,borderWidth / 2,width - borderWidth * 1.5f - headWidth, // 留出正极位置height - borderWidth / 2)canvas.drawRoundRect(batteryBodyRect, cornerRadius, cornerRadius, paint)// 2. 绘制电池正极 (在右侧外部)val headWidth = headWidthval headHeight = height / 2fbatteryHeadRect.set(width - borderWidth - headWidth,height / 2 - headHeight / 2,width - borderWidth,height / 2 + headHeight / 2)paint.style = Paint.Style.FILLcanvas.drawRect(batteryHeadRect, paint)// 3. 绘制电量部分paint.style = Paint.Style.FILLpaint.color = getPowerColor()val powerPadding = borderWidth * 1.5fval powerWidth = (batteryBodyRect.width() - powerPadding * 2) * power / 100powerRect.set(batteryBodyRect.left + powerPadding,batteryBodyRect.top + powerPadding,batteryBodyRect.left + powerPadding + powerWidth,batteryBodyRect.bottom - powerPadding)canvas.drawRoundRect(powerRect, cornerRadius / 2, cornerRadius / 2, paint)// 4. 绘制充电状态或百分比if (isCharging) {drawLightning(canvas, batteryBodyRect.width(), batteryBodyRect.height())} else if (showPercentage) {drawPercentage(canvas, batteryBodyRect.width(), batteryBodyRect.height())}}/*** 获取电量颜色*/private fun getPowerColor(): Int {return when {isCharging -> chargingColorpower < 20 -> lowPowerColorpower < 50 -> mediumPowerColorelse -> powerColor}}/*** 绘制充电闪电*/private fun drawLightning(canvas: Canvas, width: Float, height: Float) {val centerX = width / 2val centerY = height / 2val size = height / 3val offset = size * 0.05fval lightningPath1 = Path().apply {moveTo(centerX + offset, centerY - size / 2)lineTo(centerX + offset - size / 3, centerY + offset)lineTo(centerX + offset, centerY + offset)}val lightningPath2 = Path().apply {moveTo(centerX - offset, centerY - offset)lineTo(centerX - offset + size / 3, centerY - offset)lineTo(centerX - offset, centerY + size / 2)}lightningPath.reset()lightningPath.addPath(lightningPath1)lightningPath.addPath(lightningPath2)lightningPath.op(lightningPath2, Path.Op.UNION)paint.color = lightningColorpaint.style = Paint.Style.FILLcanvas.drawPath(lightningPath, paint)}/*** 绘制电量百分比*/private fun drawPercentage(canvas: Canvas, width: Float, height: Float) {val text = "$power%"val textSize = height / 2.5fpaint.color = if (power < 20) Color.WHITE else textColorpaint.textSize = textSizepaint.style = Paint.Style.FILLpaint.textAlign = Paint.Align.CENTER// 计算文本位置,避开正极val textX = width / 2val textY = height / 2 - (paint.descent() + paint.ascent()) / 2canvas.drawText(text, textX, textY, paint)}fun animatePower(targetPower: Int, duration: Long = 1000) {val animator = ValueAnimator.ofInt(power, targetPower)animator.duration = durationanimator.addUpdateListener { animation ->this.power = (animation.animatedValue as Int)}animator.start()}
}