先上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)
}
}
}