UI原型图:
实现效果:
Gif 图有点失真
功能:
1:进度条颜色和底部背景色支持自定义颜色,支持渐变色
2:数字和百分比符号支持分别设置大小和颜色
3:文字默认居中显示,支持底部显示
用法:
1:在布局文件中引入
2:在代码中调用setProgress()方法。
//设置进度
chargeProgressBar.setProgress(mProgress,true)
//设置文字位置
chargeProgressBar.setTextGravity(ChargeProgressBar.GRAVITY.GRAVITY_CENTER)
代码实现:
package com.lzk.customviewpractice.widget
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.util.TypedValue
import android.view.View
import com.lzk.customviewpractice.R
import kotlin.math.max
import kotlin.math.min
/**
* Author: LiaoZhongKai
* Date: 2020/5/25 11:27
* Description:电量显示进度条
*/
class ChargeProgressBar : View {
//bar
private var mReachBarPrimaryColor: Int = Color.parseColor("#03C9A6")
private var mReachBarSecondColor: Int = Color.parseColor("#00FBC8")
private var mUnReachBarPrimaryColor: Int = Color.parseColor("#003647")
private var mUnReachBarSecondColor: Int = Color.parseColor("#003647")
private var mCorner: Float = dp2px(1f)//dp
//文字
private var mTextColor: Int = Color.WHITE
private var mTextSize: Float = sp2px(10f)//sp
private var mTextGravity: Int = GRAVITY.GRAVITY_CENTER.value
private var mPercentageSize: Float = sp2px(8f)//sp
private var mPercentageColor: Int = Color.WHITE
private val mPercentageText = "%"
//去掉padding的真正宽度
private var mRealWidth: Float = 0f
private var mRealHeight: Float = 0f
//paint
private var mPaint = Paint()
private var mPercentagePaint = Paint()
//渐变色shader
private lateinit var mReachBarShader: Shader
private lateinit var mUnReachBarShader: Shader
private var mPath: Path = Path()
private var mMatrix: Matrix = Matrix()
//左右顶点偏离距离
private var mOffset: Float = 0f
private var mProgress: Int = 0
private val mMaxProgress: Int = 100
private var mAnimator: ObjectAnimator? = null
private var mIsAnim: Boolean = true
//存储当前进度
private var mTempProgress: Int = 0
constructor(context: Context?) : this(context,null)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs,0)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
){
obtainAttrs(attrs)
}
//获取自定义属性
private fun obtainAttrs(attrs: AttributeSet?){
val typeArray = context.obtainStyledAttributes(attrs,R.styleable.ChargeProgressBar)
mReachBarPrimaryColor = typeArray.getColor(R.styleable.ChargeProgressBar_reach_bar_primary_color,mReachBarPrimaryColor)
mReachBarSecondColor = typeArray.getColor(R.styleable.ChargeProgressBar_reach_bar_second_color,mReachBarSecondColor)
mUnReachBarPrimaryColor = typeArray.getColor(R.styleable.ChargeProgressBar_unreach_bar_primary_color,mUnReachBarPrimaryColor)
mUnReachBarPrimaryColor = typeArray.getColor(R.styleable.ChargeProgressBar_unreach_bar_second_color,mUnReachBarSecondColor)
mCorner = typeArray.getDimension(R.styleable.ChargeProgressBar_corner,dp2px(mCorner))
mTextColor = typeArray.getColor(R.styleable.ChargeProgressBar_text_color,mTextColor)
mTextSize = typeArray.getDimension(R.styleable.ChargeProgressBar_text_size,sp2px(mTextSize))
mTextGravity = typeArray.getInt(R.styleable.ChargeProgressBar_text_gravity, GRAVITY.GRAVITY_CENTER.value)
mPercentageColor = typeArray.getColor(R.styleable.ChargeProgressBar_percentage_text_color,mPercentageColor)
mPercentageSize = typeArray.getDimension(R.styleable.ChargeProgressBar_percentage_text_size,mPercentageSize)
typeArray.recycle()
mPaint.textSize = mTextSize
mPercentagePaint.textSize = mPercentageSize
mPaint.isAntiAlias = true
mPercentagePaint.isAntiAlias = true
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var widthSize = MeasureSpec.getSize(widthMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
var heightSize = MeasureSpec.getSize(heightMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val textHeight = mPaint.descent() - mPaint.ascent()
val percentageHeight = mPercentagePaint.descent() - mPercentagePaint.ascent()
val width = dp2px(150f)//dp
if (widthMode == MeasureSpec.AT_MOST){
widthSize =min(widthSize,width.toInt())+paddingStart+paddingEnd
}
val height = dp2px(16f)//dp
if (heightMode == MeasureSpec.AT_MOST){
val minHeight = min(height.toInt(),heightSize)+paddingTop+paddingBottom
val maxTextHeight = max(textHeight,percentageHeight)
heightSize = max(maxTextHeight,minHeight.toFloat()).toInt()
}
setMeasuredDimension(widthSize,heightSize)
mRealWidth = (measuredWidth - paddingStart - paddingEnd).toFloat()
mRealHeight = (measuredHeight - paddingBottom - paddingTop).toFloat()
mOffset = (mRealWidth*0.1f)/2
mReachBarShader = LinearGradient(0f,0f,0f,mRealHeight,
intArrayOf(mReachBarPrimaryColor,mReachBarSecondColor),null,Shader.TileMode.CLAMP)
mUnReachBarShader = LinearGradient(0f,0f,0f,mRealHeight,
intArrayOf(mUnReachBarPrimaryColor,mUnReachBarSecondColor),null,Shader.TileMode.CLAMP)
}
override fun onDraw(canvas: Canvas) {
canvas.save()
//平移坐标轴
canvas.translate(paddingStart.toFloat(),paddingTop.toFloat())
var ratio = mProgress.toFloat()/mMaxProgress.toFloat()
//比例超过1,按百分之百处理
if (ratio > 1) ratio = 1f
val progressX = ratio*mRealWidth
//画背景
mPaint.apply {
shader = mUnReachBarShader
style = Paint.Style.FILL
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mPath.reset()
mPath.addRoundRect(0f,0f,mRealWidth,mRealHeight,
floatArrayOf(mCorner,
mCorner,
mCorner,mCorner,
mCorner,mCorner,
mCorner,
mCorner),
Path.Direction.CCW)
//拉伸
val srcArray = floatArrayOf(0f,0f,mRealWidth,0f,0f,mRealHeight,mRealWidth,mRealHeight)
val dstArray = floatArrayOf(mOffset,0f,mRealWidth-mOffset,0f,0f,mRealHeight,mRealWidth,mRealHeight)
mMatrix.reset()
mMatrix.setPolyToPoly(srcArray,0,dstArray,0,4)
canvas.concat(mMatrix)
canvas.drawPath(mPath,this)
}else{
canvas.drawRect(progressX,0f,mRealWidth,mRealHeight,mPaint)
}
}
//画进度条
if (progressX > 0){
mPaint.apply {
shader = mReachBarShader
style = Paint.Style.FILL
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mPath.reset()
mPath.addRoundRect(0f,0f,progressX,mRealHeight,
floatArrayOf(
mCorner,
mCorner,
if (mProgress valueTextBaseLineY
//底部
GRAVITY.GRAVITY_BOTTOM.value -> mRealHeight - valueTextBaseLineY
//居中
else -> mRealHeight/2f+valueTextBaseLineY
}
val valueTextWidth = mPaint.measureText(valueText)
val percentageTextWidth = mPaint.measureText(mPercentageText)
val valueTextX = mRealWidth/2f - (valueTextWidth+percentageTextWidth)/2
//百分比符号
mPercentagePaint.color = mPercentageColor
mPercentagePaint.textSize = mPercentageSize
mPercentagePaint.isFakeBoldText = true
val percentageFontMetrics = mPercentagePaint.fontMetrics
val percentageTextBaseLineY = (percentageFontMetrics.descent - percentageFontMetrics.ascent)/2 - percentageFontMetrics.descent
val percentageTextX = valueTextX+valueTextWidth
//为什么用valueTextY - valueTextBaseLineY就能得到数字文字的中心点?
//valueTextY相当于数字文字baseline线的Y坐标,而valueTextBaseLineY相当于baseline线距离文字中线的距离,用baseline的Y坐标减去baseline线的高度就等于文件中线的Y坐标
val percentageTextY = (valueTextY - valueTextBaseLineY)+percentageTextBaseLineY
canvas.save()
canvas.translate(paddingStart.toFloat(),paddingTop.toFloat())
canvas.drawText(valueText,valueTextX,valueTextY,mPaint)
canvas.drawText(mPercentageText,percentageTextX,percentageTextY,mPercentagePaint)
canvas.restore()
}
private fun sp2px(spVal: Float): Float{
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,spVal,resources.displayMetrics)
}
private fun dp2px(dpVal: Float): Float{
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,dpVal,resources.displayMetrics)
}
enum class GRAVITY(val value: Int){
GRAVITY_TOP(1),
GRAVITY_BOTTOM(2),
GRAVITY_CENTER(3)
}
/**
* 设置文字的位置
*/
fun setTextGravity(gravity: GRAVITY){
mTextGravity = gravity.value
invalidate()
}
fun setProgress(progress: Int, anim: Boolean = true){
mIsAnim = anim
mTempProgress = progress
if (mIsAnim){
if (mAnimator != null && mAnimator!!.isRunning){
mAnimator!!.cancel()
}
mAnimator = ObjectAnimator.ofInt(this,"progress",this.mProgress,progress).setDuration(500)
mAnimator?.start()
}else{
this.mProgress = progress
mTempProgress = progress
invalidate()
}
}
private fun setProgress(progress: Int) {
this.mProgress = progress
invalidate()
}
fun getProgress(): Int = mTempProgress
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
if (mAnimator != null && mAnimator!!.isRunning){
mAnimator!!.cancel()
}
}
}