rendering
code
/**
*
* 周图表
* zrj 2020/7/14
*/
class StepWeekChart(context: Context, attrs: AttributeSet?) : View(context, attrs) {
//屏幕宽高
private var scrWidth = 0f
private var scrHeight = 0f
private var xData: Array<String> = arrayOf(
context.getString(R.string.mon),
context.getString(R.string.tue),
context.getString(R.string.wed),
context.getString(R.string.thu),
context.getString(R.string.fri),
context.getString(R.string.sat),
context.getString(R.string.sun)
)
private var data = mutableListOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)
private lateinit var paintLine: Paint
private lateinit var paintGradientLine: Paint
private lateinit var paintXText: Paint
private lateinit var paintYText: Paint
private lateinit var paintPillar: Paint
private lateinit var paintRound: Paint
private lateinit var paintBessel: Paint
private var animDuration = 500L
private var anim: ValueAnimator? = null
private var mPercent = 0f //动画进度
private var xSlider = 0f //滑块的x轴位置
private var mPath: Path
private val curveCircleRadius = 12f.dp
// the coordinates of the first curve
private val mFirstCurveStartPoint = Point()
private val mFirstCurveEndPoint = Point()
private val mFirstCurveControlPoint1 = Point()
private val mFirstCurveControlPoint2 = Point()
//the coordinates of the second curve
private var mSecondCurveStartPoint = Point()
private val mSecondCurveEndPoint = Point()
private val mSecondCurveControlPoint1 = Point()
private val mSecondCurveControlPoint2 = Point()
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
mPath = Path()
initPaint()
}
/**
* 初始化画笔
*/
private fun initPaint() {
paintLine = Paint()
paintLine.style = Paint.Style.STROKE
paintLine.strokeWidth = 1f
paintLine.color = context.colorCompat(R.color.e6e6e6_2e2e2e)
paintGradientLine = Paint()
paintGradientLine.style = Paint.Style.STROKE
paintGradientLine.strokeWidth = 2f
paintXText = Paint()
paintXText.isAntiAlias = true
paintXText.strokeWidth = 1f
paintXText.textSize = 12f.sp
paintXText.textAlign = Paint.Align.CENTER
paintXText.color = context.colorCompat(R.color.color_on_surface)
paintYText = Paint()
paintYText.isAntiAlias = true
paintYText.textSize = 12f.sp
paintYText.strokeWidth = 1f
paintYText.textAlign = Paint.Align.RIGHT
paintYText.color = context.colorCompat(R.color.secondary_666666_808080)
paintPillar = Paint()
paintPillar.style = Paint.Style.FILL
paintPillar.isAntiAlias = true
paintPillar.color = context.colorCompat(R.color.blue_7fbeff)
paintRound = Paint()
paintRound.style = Paint.Style.FILL
paintRound.isAntiAlias = true
paintRound.color = context.colorCompat(R.color.ffffff_6e6e6e)
paintBessel = Paint()
paintBessel.style = Paint.Style.FILL
paintBessel.isAntiAlias = true
paintBessel.color = context.colorCompat(R.color.f2f2f2_1d1d1d)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
scrWidth = width.toFloat()
scrHeight = height.toFloat()
ySpacing = scrHeight / 8f //y轴分8份
//底部圆滑块可以滑动的范围
xWithStart = margin + paintXText.measureText(xData[0])
xWithEnd = scrWidth - margin - paintYText.measureText(xData[0]) * 2.5f
xSpacing = (xWithEnd - xWithStart) / (data.size - 1)
xSlider = xSpacing * (week - 1) + xWithStart
}
private var mDownX = 0f
private var mDownY = 0f
private var isSlider = false
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mDownX = event.x
mDownY = event.y
isSlider = abs(event.x - xSlider) < 60f && abs(event.y - ySpacing * 7) < 60f
}
MotionEvent.ACTION_MOVE ->
if (abs(event.y - mDownY) < abs(event.x - mDownX)) {
if (isSlider) {
xSlider = event.x
invalidate()
}
}
MotionEvent.ACTION_UP -> {
if (isSlider) {
if (xSlider < xWithStart) {
xSlider = xWithStart
invalidate()
}
if (xSlider > xWithEnd) {
xSlider = xWithEnd
invalidate()
}
data.forEachIndexed { index, _ ->
val x = xWithStart + xSpacing * index
val dis = abs(x - xSlider)
if (dis < xSpacing / 2) {
xSlider = x
invalidate()
return@forEachIndexed
}
}
} else {
if (abs(event.x - mDownX) > xSpacing) {
onWeekMoveListener?.invoke(event.x > mDownX)
} else {
data.forEachIndexed { index, _ ->
val x = xWithStart + xSpacing * index
val dis = abs(x - event.x)
if (dis < xSpacing / 2) {
xSlider = x
invalidate()
return@forEachIndexed
}
}
}
}
}
}
return true
}
private val margin = 20f.dp //左右两边距离
private var xWithStart = 0f //x轴的起始点
private var xWithEnd = 0f //x轴结束点
private var ySpacing = 0f //高度分割份数后间距
private var xSpacing = 0f //x轴分割份数后间距
@SuppressLint("DrawAllocation")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//画y轴方向横线与文字
drawY(canvas)
//垂直渐变线
drawGradientLine(canvas)
//画柱子
drawPillar(canvas)
//底部
drawBessel(canvas)
//画x轴方向文字
drawX(canvas)
}
private fun drawX(canvas: Canvas) {
xData.forEachIndexed { index, s ->
val x = xWithStart + xSpacing * index
val dis = abs(x - xSlider)
var y = ySpacing * 7 - 10f.dp
if (dis < xSpacing / 2) {
paintXText.typeface = Typeface.DEFAULT_BOLD
y -= 10f.dp * (1 - dis / xSpacing)
} else {
paintXText.typeface = Typeface.DEFAULT
}
canvas.drawText(s, x, y, paintXText)
if (index == 0) {
canvas.drawText(monDay, x, y - 12f.dp, paintXText)
}
}
}
private fun drawPillar(canvas: Canvas) {
data.forEachIndexed { index, i ->
if (xSlider < xWithStart + xSpacing * index + xSpacing / 2 && xSlider > xWithStart + xSpacing * index - xSpacing / 2) {
paintPillar.color = context.colorCompat(R.color.blue_007dff)
onWeekSelectListener?.invoke(index, i)
} else {
paintPillar.color = context.colorCompat(R.color.blue_7fbeff)
}
if (i > 0) {
canvas.drawRoundRect(
RectF(
xWithStart + xSpacing * index - 15f,
ySpacing + ySpacing * 4f * (1 - (i / yMarkMax.toFloat()) * mPercent),
xWithStart + xSpacing * index + 15f,
ySpacing * 5
), 10f, 10f, paintPillar
)
}
}
}
private fun drawY(canvas: Canvas) {
val y = ySpacing + ySpacing * 4f * (1 - (stepGoal / yMarkMax.toFloat()))
for (i in 0..4) {
canvas.drawLine(
margin, ySpacing * (i + 1), scrWidth - margin,
ySpacing * (i + 1), paintLine
)
paintYText.color = context.colorCompat(R.color.secondary_666666_808080)
if (abs(y - (ySpacing * (i + 1))) > 25) { //目标重叠
canvas.drawText(
(yMarkMax - i * (yMarkMax / 4)).toString(),
scrWidth - margin,
ySpacing * (i + 1) - 10f,
paintYText
)
}
}
if (stepGoal > 0) {
paintPillar.color = context.colorCompat(R.color.blue_7fbeff)
canvas.drawLine(margin, y, scrWidth - margin, y, paintPillar)
paintYText.color = context.colorCompat(R.color.blue_7fbeff)
canvas.drawText(stepGoal.toString(), scrWidth - margin, y - 10f, paintYText)
}
}
private fun drawBessel(canvas: Canvas) {
// 第一条曲线开始点
mFirstCurveStartPoint[(xSlider - curveCircleRadius * 3).toInt()] = (ySpacing * 7).toInt()
// 第一条曲线结束点
mFirstCurveEndPoint[xSlider.toInt()] =
(ySpacing * 7 - curveCircleRadius - curveCircleRadius / 4).toInt()
// 第二条开始点
mSecondCurveStartPoint = mFirstCurveEndPoint
mSecondCurveEndPoint[(xSlider + curveCircleRadius * 3).toInt()] = (ySpacing * 7).toInt()
// 第一条控制点
mFirstCurveControlPoint1[(mFirstCurveStartPoint.x + curveCircleRadius + curveCircleRadius / 4).toInt()] =
mFirstCurveStartPoint.y
mFirstCurveControlPoint2[(mFirstCurveEndPoint.x - curveCircleRadius * 2 + curveCircleRadius).toInt()] =
mFirstCurveEndPoint.y
// 第二条控制点
mSecondCurveControlPoint1[(mSecondCurveStartPoint.x + curveCircleRadius * 2 - curveCircleRadius).toInt()] =
mSecondCurveStartPoint.y
mSecondCurveControlPoint2[(mSecondCurveEndPoint.x - curveCircleRadius - curveCircleRadius / 4).toInt()] =
mSecondCurveEndPoint.y
mPath.reset()
mPath.moveTo(0f, ySpacing * 7)
mPath.lineTo(mFirstCurveStartPoint.x.toFloat(), mFirstCurveStartPoint.y.toFloat())
mPath.cubicTo(
mFirstCurveControlPoint1.x.toFloat(), mFirstCurveControlPoint1.y.toFloat(),
mFirstCurveControlPoint2.x.toFloat(), mFirstCurveControlPoint2.y.toFloat(),
mFirstCurveEndPoint.x.toFloat(), mFirstCurveEndPoint.y.toFloat()
)
mPath.cubicTo(
mSecondCurveControlPoint1.x.toFloat(), mSecondCurveControlPoint1.y.toFloat(),
mSecondCurveControlPoint2.x.toFloat(), mSecondCurveControlPoint2.y.toFloat(),
mSecondCurveEndPoint.x.toFloat(), mSecondCurveEndPoint.y.toFloat()
)
mPath.lineTo(scrWidth, ySpacing * 7)
mPath.lineTo(scrWidth, scrHeight)
mPath.lineTo(0f, scrHeight)
mPath.close()
//底部灰色
canvas.drawPath(mPath, paintBessel)
//底部滑块
canvas.drawCircle(xSlider, ySpacing * 7 + 5f, curveCircleRadius, paintRound)
}
private var yMarkMax = 1 //Y轴刻度最大值(根据设置的数据自动赋值)
private var monDay = "" //每周一的日期
private var stepGoal = 0
private var week = 0
fun setValue(value: MutableList<Float>, monDay: String, goal: Int, week: Int): StepWeekChart {
this.monDay = monDay
this.stepGoal = goal
this.week = week
data.clear()
data.addAll(value)
xSlider = xSpacing * (week - 1) + xWithStart
var yMark = (data.maxOrNull() ?: 4).toInt() / 4 + 1 // 4 3 2 1 0 每个刻度间距 5个刻度4个间距
val mark = yMark.toString().substring(0, 1).toInt() + 1
when (yMark.toString().length) {
1 ->
//yMark = 1、2、5、10
yMark =
if (yMark == 3 || yMark == 4 || yMark == 6 || yMark == 7 || yMark == 8 || yMark == 9) if (yMark == 3 || yMark == 4) 5 else 10 else yMark
2 -> yMark = mark * 10
3 -> yMark = mark * 100
4 -> yMark = mark * 1000
5 -> yMark = mark * 10000
6 -> yMark = mark * 100000
}
yMarkMax = yMark * 4
startAnimation()
return this
}
private fun startAnimation() {
anim = ValueAnimator.ofObject(AngleEvaluator(), 0f, 1f)
anim?.interpolator = AccelerateDecelerateInterpolator()
anim?.addUpdateListener { animation ->
mPercent = animation.animatedValue as Float
postInvalidate()
}
anim?.duration = animDuration
anim?.start()
}
private fun drawGradientLine(canvas: Canvas) {
val mLinearGradient = LinearGradient(
xSlider, ySpacing, xSlider, ySpacing * 6,
intArrayOf(
context.colorCompat(R.color.ffffff_262626), Color.parseColor("#0e83ff"),
context.colorCompat(R.color.ffffff_262626)
), null, Shader.TileMode.MIRROR
)
paintGradientLine.shader = mLinearGradient
if (ySpacing > 0) {
canvas.drawLine(xSlider, ySpacing, xSlider, ySpacing * 6, paintGradientLine)
}
}
private var onWeekSelectListener: ((index: Int, value: Float) -> Unit)? = null
fun setOnWeekSelectListener(l: ((index: Int, value: Float) -> Unit)): StepWeekChart {
this.onWeekSelectListener = l
return this
}
private var onWeekMoveListener: ((isPre: Boolean) -> Unit)? = null
fun setOnWeekMoveListener(l: ((index: Boolean) -> Unit)): StepWeekChart {
this.onWeekMoveListener = l
return this
}
}
Copy the code