Android自定义柱状图

先上ui效果图

1. 分析UI稿 先定义一些属性

java 复制代码
private var mouthTextColor = Color.BLACK  //底部月份文字颜色
private var bottomEndColor = Color.BLACK //柱状图渐变结束颜色
private var bottomStartColor = Color.BLACK //柱状图渐变起始颜色
private var chatTopTextColor = Color.BLACK//顶部月份文字颜色
//字体大小 
private var mouthTextSize: Float=DensityUtils.sp2px(context,12f).toFloat()
//月份最高31天  高度为 天数*倍数  31*4 
private val multiple:Int=DensityUtils.dp2px( 4f)
//柱状图宽度 
private var mChartWidth = DensityUtils.dp2px( 8f)
//柱状图底部距离布局底部距离 
private var mChartMarginBottom =DensityUtils.dp2px( 27f)
//柱状图顶部距离顶部文本的距离 
private var mChartMarginTopTitle =DensityUtils.dp2px( 4f)
//柱状图圆角 
private var mChartRadius =DensityUtils.dp2px( 8f).toFloat()
// 柱状图左右间距 
private var mStartWidth = DensityUtils.dp2px( 30f)

2. 测量出布局所需的width和height

高度:月份打卡数最高为31 依据蓝湖比例 一天等于4dp 柱状图最高 31*4 (dp)然后加上margin+padding+底部的月份文字间距

宽度:因为如果一屏显示不全,需要左右滑动,需要外层嵌套一个HorizontalScrollView 柱状图宽度12+左边间距12+最右边间距+margin+padding

kotlin 复制代码
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    val widthSize = MeasureSpec.getSize(widthMeasureSpec)
    val widthMode = MeasureSpec.getMode(widthMeasureSpec)
    val heightSize = MeasureSpec.getSize(heightMeasureSpec)
    val heightMode = MeasureSpec.getMode(heightMeasureSpec)

    var width = widthSize     
	var height = heightSize      
	// 如果宽度是 wrap_content 或者不是精确尺寸,需要计算实际需要的宽度     
	if (widthMode != MeasureSpec.EXACTLY) {
        width = calculateDesiredWidth()
    }

    // 高度如果不是精确尺寸,可以根据内容或者默认高度进行计算     
	if (heightMode != MeasureSpec.EXACTLY) {
        height = calculateDesiredHeight()
    }
    setMeasuredDimension(width, height)
}
private fun calculateDesiredWidth(): Int {
    val numColumns = 12 // 假设有12个月份     
	val columnWidth = mChartWidth  // 每个柱状图的宽度加上间距    
	// 计算内容所需的总宽度     
	val contentWidth = numColumns * (columnWidth + mStartWidth)
    // 加上空隙最右侧 宽度     
	val desiredWidth = contentWidth +mStartWidth+marginLeft+marginRight+paddingStart+paddingEnd      
	return desiredWidth 
	}

private fun calculateDesiredHeight(): Int {
    // 根据内容计算需要的高度,最高的为31个月  每月multiple4dp  再加上文本12sp 和间距4dp     
	val maxMouthDay = 31 // 31个月     r
	eturn ((maxMouthDay * multiple)+calculateTextHeight()+mChartMarginTopTitle+mChartMarginBottom+marginTop+
	marginBottom+paddingStart+paddingEnd).toInt()// 这里需要根据实际情况计算 
	}

开始画布局

1.先画柱状图 先定义出柱状图线性渐变 然后遍历12次 因为月份有12个月 startMargin 等于 startMargin+mStartWidth左边间距+ mChartWidth柱状图宽度 依次累加

  • var startMargin=0
  • startMargin += mStartWidth + mChartWidth
  • val rectF = RectF()
  • rectF.left = startMargin-mChartWidth.toFloat()
  • rectF.right = (startMargin ).toFloat()
  • rectF.bottom = (mHeight - mChartMarginBottom).toFloat()
  • rectF.top = (mHeight - mChartMarginBottom - list[i] * multiple).toFloat()
  • canvas.drawRoundRect(rectF, mChartRadius, mChartRadius, mChartPaint)

2.底部月份文本 先测量出文本的宽高 在通过mBound获取

  • mPaint.getTextBounds( "${i + 1}", 0, i.toString().length, mBound)
  • drawText (String text, float x, float y, Paint paint)
  • x 是文本左侧的 X 坐标。
  • y 是文本基线的 Y 坐标
  • canvas.drawText("${i + 1}月", ( startMargin-mChartWidth * 1 / 2 ).toFloat(), (mHeight - mBound.height() * 1 / 2).toFloat(), mPaint)

画柱状图顶部数字 和月份同理

  • val topTextBound = Rect()
  • mPaint.getTextBounds( "${list[i]}", 0, list[i].toString().length, topTextBound)
  • canvas.drawText("${list[i]}", ( startMargin-mChartWidth * 1 / 2).toFloat(), (mHeight - (list[i] * multiple)-topTextBound.height() * 1 / 2-mChartMarginBottom-mChartMarginTopTitle).toFloat(), mPaint)
scss 复制代码
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    var startMargin=0     for (i in 0..11) {
        mChartPaint.style = Paint.Style.FILL         
		if (list.isNotEmpty()) {
            startMargin += mStartWidth + mChartWidth        
			var mBound = Rect()
            //画月份数字             
			mPaint.color = mouthTextColor            
			mPaint.textSize = mouthTextSize            
			mPaint.textAlign = Paint.Align.CENTER           
			mPaint.getTextBounds( "${i + 1}", 0, i.toString().length, mBound)
            canvas.drawText("${i + 1}月", ( startMargin-mChartWidth * 1 / 2 ).toFloat(),
                (mHeight  - mBound.height() * 1 / 2).toFloat(), mPaint)


            //画柱状图顶部数字            
			mTopTitlePaint.textSize = mouthTextSize            
			mPaint.textAlign = Paint.Align.CENTER            
			mPaint.color = chatTopTextColor             
			val topTextBound = Rect()
            mPaint.getTextBounds( "${list[i]}", 0, list[i].toString().length, topTextBound)
            canvas.drawText("${list[i]}", ( startMargin-mChartWidth * 1 / 2).toFloat(),
                (mHeight  -  (list[i] * multiple)-topTextBound.height() * 1 / 2-
				mChartMarginBottom-mChartMarginTopTitle).toFloat(), mPaint)

            //画柱状图             
			val lg=LinearGradient(
                mChartWidth.toFloat(),
                (mHeight - mChartMarginBottom).toFloat(),
                mChartWidth.toFloat(),
                (mHeight - mChartMarginBottom - list[i] * multiple).toFloat(),
                bottomStartColor,
                bottomEndColor,
                Shader.TileMode.CLAMP             
				)
            mChartPaint.setShader(lg)
            val rectF = RectF()
            rectF.left = startMargin-mChartWidth.toFloat()
			rectF.right = (startMargin ).toFloat()
            rectF.bottom = (mHeight - mChartMarginBottom).toFloat()
            rectF.top = (mHeight - mChartMarginBottom - list[i] * multiple).toFloat()
            canvas.drawRoundRect(rectF, mChartRadius, mChartRadius, mChartPaint)
        }
    }
}
相关推荐
未来之窗软件服务3 分钟前
android studio无法下载,Could not GET xxx, Received status code 400
android·ide·android studio
main_Java36 分钟前
护眼模式浓度调整到最低
android
慢慢_飞2 小时前
Android集成FCM(Firebace Cloud Messaging )
android
未来之窗软件服务2 小时前
国内镜像android studio
android·ide·android studio
Crossoads3 小时前
【汇编语言】数据处理的两个基本问题(三) —— 汇编语言的艺术:从div,dd,dup到结构化数据的访问
android·linux·运维·服务器·汇编·机器学习·数据挖掘
Crossoads3 小时前
【汇编语言】数据处理的两个基本问题(二) —— 解密汇编语言:数据长度与寻址方式的综合应用
android·java·开发语言·javascript·汇编·数据挖掘·c#
吃汉堡吃到饱7 小时前
【Android】EventBus事件总线用法浅析
android·开发语言
problc9 小时前
Flutter中的Material Theme完全指南:从入门到实战
android·flutter
xiayuexingkong13 小时前
悬浮窗,ViewPager2内嵌套RecyclerView,RecyclerView高度异常的问题分析
android
绘绘~14 小时前
android studio new flutter project-运行第一个flutter项目
android·flutter·android studio