版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/9708588.html
前言
此篇博客提供一个个人实现的柱状图View,并且相关功能已经封装好,可以直接复制代码使用。
效果图
静态效果图
动态效果图
代码
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.util.TypedValue
import android.view.View
import java.util.concurrent.atomic.AtomicBoolean
/**
* 柱状图View
*/
class HistogramView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private val mProgressList = mutableListOf<Pair<String, Float>>()
private val mRectList = mutableListOf<RectF>()
private var mWidth = 0
private var mHeight = 0
private var mRoundX = 0f
private var mRoundY = 0f
private val mPaint by lazy { Paint() }
private val mTextPaint by lazy { Paint() }
private var mIsSetItemWidth = false
private var mSetItemWidth = 0
private var mInterval = 0
private var mTextSize = 14f
private var mTextAndHistogramTopMargin = 5f
private var mIsAnimation = AtomicBoolean(false)
private var mAnimationSpeed = 25L
private var mAnimationValue = 0.01f
init {
mPaint.color = Color.GREEN
mPaint.setFlags(Paint.ANTI_ALIAS_FLAG)
mTextPaint.color = Color.BLACK
mTextPaint.textSize = mTextSize
mTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG)
mTextPaint.textAlign = Paint.Align.CENTER
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
mWidth = MeasureSpec.getSize(widthMeasureSpec)
mHeight = MeasureSpec.getSize(heightMeasureSpec)
if (mIsSetItemWidth) {
calculatedCoordinates(mSetItemWidth, mInterval)
} else {
calculatedCoordinates()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (mIsAnimation.get()) {
drawAnimationHistogram(canvas)
} else {
drawHistogram(canvas)
}
}
/**
* 刷新内容进度
* 第一个参数 是item柱状图下面的文字名称
* 第二个参数 是每个柱状图的进度 进度范围0.00-1
*/
fun refreshProgressList(list: List<Pair<String, Float>>?) {
mProgressList.clear()
list?.let {
mProgressList.addAll(list)
}
postInvalidate()
}
/**
* 设置item的文字颜色
*/
fun setItemTextColor(color: Int) {
mTextPaint.color = color
postInvalidate()
}
/**
* 设置item的文字大小
*/
fun setItemTextSize(textSize: Float) {
mTextPaint.textSize = textSize
postInvalidate()
}
/**
* 设置文字与柱状图的间距
*/
fun setTextAndHistogramTopMargin(margin: Float) {
mTextAndHistogramTopMargin = margin
postInvalidate()
}
/**
* 设置柱状图圆角
*/
fun setHistogramRound(roundX: Float, roundY: Float) {
mRoundX = roundX
mRoundY = roundY
postInvalidate()
}
/**
* 设置柱状图颜色
*/
fun setHistogramColor(color: Int) {
mPaint.color = color
postInvalidate()
}
/**
* 设置柱状图渐变色
*/
fun setHistogramColor(startColor: Int, endColor: Int) {
val linearGradient = LinearGradient(0f, 100f, 1000f, mHeight.toFloat(), startColor, endColor, Shader.TileMode.CLAMP)
mPaint.shader = linearGradient
postInvalidate()
}
/**
* 设置柱状图渐变色
*/
fun setHistogramColor(colors: IntArray, positions: FloatArray) {
val linearGradient = LinearGradient(0f, 100f, 1000f, mHeight.toFloat(), colors, positions, Shader.TileMode.CLAMP)
mPaint.shader = linearGradient
postInvalidate()
}
/**
* 设置柱状图宽度与间隔宽度
* 单位DP
*/
fun setHistogramWidthDpAndInterval(itemWidthDp: Float, intervalDp: Float) {
mIsSetItemWidth = true
mSetItemWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, itemWidthDp, resources.displayMetrics).toInt()
mInterval = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, intervalDp, resources.displayMetrics).toInt()
calculatedCoordinates(mSetItemWidth, mInterval)
postInvalidate()
}
/**
* 设置无需设置item宽度,自动均分View的宽度计算获得Item宽度
*/
fun setAverageItemWidth() {
mIsSetItemWidth = false
calculatedCoordinates()
postInvalidate()
}
/**
* 启用动画
* [speed] 动画的执行速度,建议设置范围100-25毫秒之间
*/
fun enableAnimation(speed:Long){
mIsAnimation.set(true)
mAnimationSpeed = speed
postInvalidate()
}
/**
* 取消动画
*/
fun cancelAnimation(){
mIsAnimation.set(false)
postInvalidate()
}
/**
* 以均分宽度的方式计算坐标值
*/
private fun calculatedCoordinates() {
mRectList.clear()
val intervalCount = mProgressList.size * 2
val itemWidth = mWidth / intervalCount
//文字的总高度 文字大小 + 文字与柱状图的之间的间隔
val textHeight = mTextSize + mTextAndHistogramTopMargin
//柱状图的总高度 View的高度 - 文字的高度
val histogramHeight = mHeight - textHeight
for (i in 0..intervalCount) {
if (i % 2 != 0) {
val rect = RectF()
val position = i / 2 //获取除数商,i是奇数除以2的商正好是数据的位置
rect.top = histogramHeight - (histogramHeight.toFloat() * mProgressList[position].second)
rect.bottom = histogramHeight
rect.left = i * itemWidth.toFloat()
rect.right = (i + 1) * itemWidth.toFloat()
mRectList.add(rect)
}
}
Log.e("zh", "以均分宽度的方式计算坐标值,计算完成")
}
/**
* 以设置item的宽度与间隔的方式计算坐标值
*/
private fun calculatedCoordinates(width: Int, interval: Int) {
mRectList.clear()
val intervalCount = mProgressList.size * 2
val horizontalMargin = ((mWidth - intervalCount * width) + intervalCount * interval) / 2
//文字的总高度 文字大小 + 文字与柱状图的之间的间隔
val textHeight = mTextSize + mTextAndHistogramTopMargin
//柱状图的总高度 View的高度 - 文字的高度
val histogramHeight = mHeight - textHeight
for (i in 0..intervalCount) {
if (i % 2 != 0) {
val rect = RectF()
val position = i / 2 //获取除数商,i是奇数除以2的商正好是数据的位置
rect.top = histogramHeight - (histogramHeight.toFloat() * mProgressList[position].second)
rect.bottom = histogramHeight
rect.left = i * width.toFloat() + horizontalMargin
rect.right = (i + 1) * width.toFloat() + horizontalMargin
//减少间隔距离
if (i > 0) {
rect.left = rect.left - i * interval
rect.right = rect.right - i * interval
}
mRectList.add(rect)
}
}
Log.e("zh", "以设置item的宽度与间隔的方式计算坐标值,计算完成")
}
/**
* 绘制动画的柱状图
*/
private fun drawAnimationHistogram(canvas: Canvas){
mRectList.forEachIndexed { index, rectF ->
val top = rectF.bottom - ((rectF.bottom - rectF.top) * mAnimationValue)
canvas.drawRoundRect(rectF.left, top, rectF.right, rectF.bottom, mRoundX, mRoundY, mPaint)
val textX = rectF.centerX()
//因为文字会显示不完整,所以高度-2
canvas.drawText(mProgressList[index].first, textX, mHeight.toFloat() - 2, mTextPaint)
}
mAnimationValue += 0.1f
if (mAnimationValue >= 1){
mIsAnimation.set(false)
}
postInvalidateDelayed(mAnimationSpeed)
}
/**
* 绘制柱状图
*/
private fun drawHistogram(canvas: Canvas) {
mRectList.forEachIndexed { index, rectF ->
canvas.drawRoundRect(rectF, mRoundX, mRoundY, mPaint)
val textX = rectF.centerX()
//因为文字会显示不完整,所以高度-2
canvas.drawText(mProgressList[index].first, textX, mHeight.toFloat() - 2, mTextPaint)
}
}
}
end
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/9708588.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具