当前位置: 首页 > news >正文

自定义电池电量显示控件 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()}
}

相关文章:

  • express的模板handlebars用app.engine()创建配置和用exphbs.create()的区别
  • Vue3后代组件多祖先通讯设计方案
  • MATLAB基础应用精讲-【基础知识篇】发布和共享 MATLAB 代码
  • ThinkPHP6模型中多组条件逻辑或Or查询的使用
  • BiliNote:开源的AI视频笔记生成工具,让知识提取与分享更高效——跨平台自动生成结构化笔记,实现从视频到Markdown的智能转化
  • Shell脚本-for循环应用案例
  • 算法训练营 Day1
  • OAuth2AuthorizationEndpointFilter类介绍、应用场景和示例代码
  • 第3讲、大模型如何理解和表示单词:词嵌入向量原理详解
  • Java面试高频问题(26-28)
  • AI新战局:Gemini 2.5 Pro强势挑战OpenAI o3,谁是真“全能”?“锯齿AGI”时代已来临?
  • 中国250米土壤质地类型数据
  • 筑牢数字防线:商城系统安全的多维守护策略
  • Ubuntu18.04更改时区(图文详解)
  • 【Python数据库与后端开发】从ORM到RESTful API
  • 前端基础之《Vue(11)—自定义指令》
  • 全栈国产化信创适配,构建安全可控的呼叫中心系统
  • 【安全扫描器原理】TCP/IP协议编程
  • 力扣面试150题--环形链表和两数相加
  • 【滑动窗口+哈希表/数组记录】Leetcode 438. 找到字符串中所有字母异位词
  • 黄晓丹:用“诗心”找到生存的意义
  • 上海出台灵活就业人员公积金新政:不限户籍、提取自由,6月起施行
  • 利物浦提前四轮英超夺冠,顶级联赛冠军数追平曼联
  • “天链”继续上新!长三乙火箭成功发射天链二号05星
  • 今年3月全国查处违反中央八项规定精神问题16994起
  • 广汽集团一季度净亏损7.3亿元,同比转亏,总销量下滑9%