目录

效果展示
实现原理
- 轴线:轴线的实现原理比较简单,就是使用线拼出X、Y轴的路径。
- 柱形图:柱形图的实现相对复杂,首先需要根据X轴的长度和数据的size计算出每个矩形的宽度,然后根据传入的最大值计算出每个柱形图的高度公式为:柱形图的绘制高度 = Y轴长度 / 传入的最大值 * 柱形图代表的值的大小,然后将计算出的每一个柱形图(矩形Rect)添加到集合当中去。
- 柱形图动画:柱形图动画使用的是ValueAnimator,与普通的柱形图不一样的是在每次添加矩形的时候都需要为每一个矩形增加一个动画。
- 柱形图文字描述:在向柱形图集合中添加矩形的时候,需要根据矩形当前位置同时创建一个用来画文字描述的点的对象,然后根据这些点的位置来画出相应的文字描述。
代码展示
class HistogramView : View {
private var paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mPaintText:Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mMargen: Float = 40F
private var mCsysPath: Path = Path()
private var mRectList:ArrayList<HistogramRectItem>? = null
private var mDescTextSize:Float = 20F
private var mIsOpenAnim:Boolean = false
private var mHistogramList:ArrayList<RectF>? = null
private var mRectColor:Int = Color.BLUE
private var mAxisColor:Int = Color.BLUE
private var mRectDescTextColor:Int = Color.BLUE
init {
paint.strokeWidth = 3F
paint.color = mAxisColor
paint.strokeCap = Paint.Cap.ROUND
paint.strokeJoin = Paint.Join.ROUND
paint.style = Paint.Style.STROKE
mPaintText.textSize = mDescTextSize
mPaintText.textAlign = Paint.Align.CENTER
mPaintText.strokeWidth = 3F
mPaintText.color = mRectDescTextColor
}
constructor(context: Context) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){
initAttr(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
initAttr(attrs)
}
@SuppressLint("Recycle")
fun initAttr(attrs: AttributeSet?){
val ta = context.obtainStyledAttributes(attrs, R.styleable.HistogramView)
mAxisColor = ta.getColor(R.styleable.HistogramView_axisColor, mAxisColor)
mRectColor = ta.getColor(R.styleable.HistogramView_rectColor,mRectColor)
mRectDescTextColor = ta.getColor(R.styleable.HistogramView_rectDescTextColor,mRectDescTextColor)
mDescTextSize = ta.getFloat(R.styleable.HistogramView_rectTextSize,mDescTextSize)
mPaintText.textSize = mDescTextSize
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
initCsysPath()
}
private fun initCsysPath(){
mCsysPath.reset()
mCsysPath.moveTo(mMargen - 10, mMargen + 10)
mCsysPath.lineTo(mMargen, mMargen)
mCsysPath.lineTo(mMargen + 10, mMargen + 10)
mCsysPath.moveTo(mMargen, mMargen)
mCsysPath.lineTo(mMargen,height - mMargen)
mCsysPath.lineTo(width - mMargen,height - mMargen)
mCsysPath.lineTo(width - mMargen - 10,height - mMargen - 10)
mCsysPath.moveTo(width - mMargen,height - mMargen)
mCsysPath.lineTo(width - mMargen - 10,height - mMargen + 10)
}
fun setAxisColor(color:Int){
mAxisColor = color
invalidate()
}
fun setRectColor(color:Int){
mRectColor = color
invalidate()
}
fun setRectDescTextColor(color:Int){
mPaintText.color = color
invalidate()
}
fun setDescTextSize(textSize:Float){
mDescTextSize = textSize
mPaintText.textSize = mDescTextSize
for(histioramitem in mRectList!!){
histioramitem.pointF!!.y = height - mMargen + mDescTextSize
}
invalidate()
}
fun initRectPath(rectList:List<HistogramRectItem>,maxVal:Float,isOpenAnim:Boolean,animDuration:Long){
mIsOpenAnim = isOpenAnim
mRectList = rectList as ArrayList<HistogramRectItem>
val yLength = width - (mMargen*2)
val xLength = height - (mMargen*2)
val yRealUseLength = yLength - (20*(rectList.size+1))
val rectItemWidth = yRealUseLength/rectList.size
val partHeight = xLength/maxVal
if(mHistogramList == null){
mHistogramList = ArrayList()
}
mHistogramList!!.clear()
if(mIsOpenAnim){
for ((i,rect) in mRectList!!.withIndex()){
val left = mMargen + 20 + 20 * i + i * rectItemWidth
val top = height - partHeight * rect.rectValue - mMargen
val right = left + rectItemWidth
val bottom = height - mMargen
val rectF = RectF(left,top,right,bottom)
rect.pointF = PointF(left+rectItemWidth/2,bottom + mDescTextSize)
mHistogramList!!.add(rectF)
val valueAnimator = ValueAnimator.ofFloat(height - mMargen,top)
valueAnimator.duration = animDuration
valueAnimator.addUpdateListener {
val value:Float = it.animatedValue as Float
rectF.top = value
invalidate()
}
valueAnimator.start()
}
}else{
for ((i,rect) in mRectList!!.withIndex()){
val left = mMargen + 20 + 20 * i + i * rectItemWidth
val top = height - partHeight * rect.rectValue - mMargen
val right = left + rectItemWidth
val bottom = height - mMargen
val rectF = RectF(left,top,right,bottom)
rect.pointF = PointF(left+rectItemWidth/2,bottom + mDescTextSize)
mHistogramList!!.add(rectF)
}
invalidate()
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.style = Paint.Style.STROKE
paint.color = mAxisColor
canvas.drawPath(mCsysPath, paint)
paint.style = Paint.Style.FILL
paint.color = mRectColor
if(mHistogramList!=null){
for(rect in mHistogramList!!){
canvas.drawRect(rect,paint)
}
}
if(mRectList!=null){
for(histogramitem in mRectList!!){
canvas.drawText(histogramitem.rectText,histogramitem.pointF!!.x,histogramitem.pointF!!.y,mPaintText)
}
}
}
}
- 柱形图Item实体类(HistogramRectItem)
class HistogramRectItem {
var rectValue:Float = 0f
var rectText:String = ""
var pointF:PointF? = null
constructor()
constructor(rectValue:Float,rectText:String){
this.rectValue = rectValue
this.rectText = rectText
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HistogramView">
<attr name="axisColor" format="color"/>
<attr name="rectColor" format="color"/>
<attr name="rectDescTextColor" format="color"/>
<attr name="rectTextSize" format="float"/>
</declare-styleable>
</resources>