package com.sq.weather.ui.view
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.DashPathEffect
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Shader
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.blankj.utilcode.util.ConvertUtils
import com.sq.common_module.common.bean.Month
import java.text.SimpleDateFormat
import java.util.Date
class TempTrendView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val mYXAxisTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mYXAxisTextSize = ConvertUtils.sp2px(10f).toFloat()
private var mYXAxisTextColor = Color.parseColor("#AAAAAA")
private val mYXAxisTextBounds = Rect()
private val mStrTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mStrTextSize = ConvertUtils.sp2px(10f).toFloat()
private var mStrTextColor = Color.parseColor("#FFFFFF")
private val mStrTextRect = Rect()
private val mPathPaint = Paint(Paint.ANTI_ALIAS_FLAG) //曲线画笔
private val mPath = Path()
private val mBaseShadowPaint = Paint(Paint.ANTI_ALIAS_FLAG) //填充画笔
private val mDashLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mDashLineColor = Color.parseColor("#AAAAAA")
private var mDashLineWidth = 0.5f
private val mSelectLinePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var mSelectLineColor = Color.parseColor("#3097FC")
private var mSelectLineWidth = 2f
private val mMarginLeftAndBottom = 20f
private val mMarginTextLine = 10f //字体和虚线的间距
private var mMonthList = mutableListOf<Month>()
private var maxTemp: Int = 0 //最大温度
private var minTemp: Int = 0 //最小温度
private var mCurrentDay = SimpleDateFormat("yyyy-MM-dd").format(Date())
private val mMonthPoint = mutableListOf<PointF>()
private val smoothness = 0.36f //折线的弯曲率
init {
initView(context, attrs)
}
private fun initView(context: Context, attrs: AttributeSet?) {
// val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ItemView)
// setAttrs(attrs, typedArray)
initPaint()
}
private fun initPaint() {
mYXAxisTextPaint.style = Paint.Style.FILL_AND_STROKE
mYXAxisTextPaint.color = mYXAxisTextColor
mYXAxisTextPaint.textSize = mYXAxisTextSize
mStrTextPaint.style = Paint.Style.FILL_AND_STROKE
mStrTextPaint.color = mStrTextColor
mStrTextPaint.textSize = mStrTextSize
mDashLinePaint.color = mDashLineColor
mDashLinePaint.strokeWidth = mDashLineWidth
mDashLinePaint.pathEffect = DashPathEffect(floatArrayOf(5f, 5f), 0f)
mSelectLinePaint.color = mSelectLineColor
mSelectLinePaint.strokeWidth = mSelectLineWidth
mPathPaint.color = mSelectLineColor
mPathPaint.strokeCap = Paint.Cap.ROUND
mPathPaint.strokeJoin = Paint.Join.ROUND
mPathPaint.style = Paint.Style.STROKE
mPathPaint.strokeWidth = 5f
mBaseShadowPaint.color = Color.WHITE
mBaseShadowPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
toGetPointF() //获取x和y的坐标
onDrawLine(canvas)
drawArea(canvas)
onDrawLineX(canvas)
onDrawLineY(canvas)
}
private fun toGetPointF() {
mMonthPoint.clear()
//y轴字体宽度
val textWidth = mYXAxisTextPaint.measureText("${maxTemp}°")
//获取x间隔
val xValue = (width.toFloat() - textWidth) / mMonthList.size - 1
val yValue = (height - mYXAxisTextBounds.height() - mMarginLeftAndBottom - mMarginTextLine) / 3
val oneTemp = yValue / (maxTemp - minTemp)
mMonthList.forEachIndexed { index, month ->
//获取点的x轴坐标
val lineX = index * xValue + textWidth + mMarginLeftAndBottom + mMarginTextLine
val pointY = yValue + oneTemp * (maxTemp - (month.day?.temperature?.toInt() ?: 0))
mMonthPoint.add(PointF(lineX, pointY))
}
}
private fun onDrawLine(canvas: Canvas) {
// 清除路径
mPath.reset()
var lX = 0f
var lY = 0f
if (mMonthPoint.isNotEmpty()) {
mPath.moveTo(mMonthPoint[0].x, mMonthPoint[0].y)
for (i in 1 until mMonthPoint.size) {
val p = mMonthPoint[i]
val firstPointF = mMonthPoint[i - 1]
val x1 = firstPointF.x + lX
val y1 = firstPointF.y + lY
val secondPointF: PointF = mMonthPoint[if (i + 1 < mMonthPoint.size) i + 1 else i]
lX = (secondPointF.x - firstPointF.x) / 2 * smoothness
lY = (secondPointF.y - firstPointF.y) / 2 * smoothness
val x2 = p.x - lX
var y2 = p.y - lY
if (y1 == p.y) {
y2 = y1
}
mPath.cubicTo(x1, y1, x2, y2, p.x, p.y)
}
canvas.drawPath(mPath, mPathPaint)
}
}
private fun drawArea(canvas: Canvas) {
val mShader = LinearGradient(0f, 0f, 0f,
measuredHeight.toFloat(), intArrayOf(Color.parseColor("#330E9CFF"), Color.parseColor("#330E9CFF")), floatArrayOf(0.5f, 0.5f), Shader.TileMode.REPEAT);
mBaseShadowPaint.shader = mShader
if(mMonthPoint.isEmpty()) return
val y = height - mYXAxisTextBounds.height() - mMarginLeftAndBottom - mMarginTextLine
mPath.lineTo(mMonthPoint[mMonthPoint.size - 1].x, y)
mPath.lineTo(mMonthPoint[0].x, y)
canvas.drawPath(mPath, mBaseShadowPaint)
}
private fun onDrawLineX(canvas: Canvas) {
//y轴字体宽度
val textWidth = mYXAxisTextPaint.measureText("${maxTemp}°")
//获取x间隔
val xValue = (width.toFloat() - textWidth) / mMonthList.size - 1
mMonthList.forEachIndexed { index, month ->
val date = month.date?.split("-")
date?.let {
val dateText = "${it[1]}/${it[2]}"
mYXAxisTextPaint.getTextBounds(dateText, 0, dateText.length, mYXAxisTextBounds)
if (index % 5 == 0 || index == mMonthList.lastIndex) {
canvas.drawText(dateText, index * xValue + textWidth - mMarginLeftAndBottom - mMarginTextLine , height.toFloat() - mMarginLeftAndBottom, mYXAxisTextPaint)
}
val lineX = index * xValue + textWidth + mMarginLeftAndBottom + mMarginTextLine
val lineY = height - mYXAxisTextBounds.height() - mMarginLeftAndBottom - mMarginTextLine
if(month.date == mCurrentDay) {
canvas.drawLine(lineX, 0f, lineX, lineY, mSelectLinePaint)
canvas.drawCircle(mMonthPoint[index].x, mMonthPoint[index].y, 10f, mStrTextPaint)
val strText = "${dateText}日 最高${month.day?.temperature}°"
mStrTextPaint.getTextBounds(strText, 0, strText.length, mStrTextRect)
//绘制温度背景
val textBgRect = RectF()
textBgRect.left = mMonthPoint[index].x - mStrTextRect.width() / 2 - 20
textBgRect.right = mMonthPoint[index].x + mStrTextRect.width() / 2 + 20
textBgRect.bottom = mMonthPoint[index].y - mStrTextRect.height() / 2 - 10
textBgRect.top = textBgRect.bottom - mStrTextRect.height() - 20
canvas.drawRoundRect(textBgRect, 30f,30f, mSelectLinePaint)
canvas.drawText(strText, textBgRect.centerX() - mStrTextRect.width() / 2,textBgRect.centerY() + mStrTextRect.height() / 2 - 5, mStrTextPaint)
}
canvas.drawLine(lineX, 0f, lineX, lineY, mDashLinePaint)
}
}
}
private fun onDrawLineY(canvas: Canvas) {
val yValue = (height - mYXAxisTextBounds.height() - mMarginLeftAndBottom - mMarginTextLine) / 3
yValue / (maxTemp - minTemp)
mYXAxisTextPaint.getTextBounds("${maxTemp}°", 0, "${maxTemp}°".length, mYXAxisTextBounds)
for (index in 0..3) {
if (index == 0) continue
canvas.drawLine(mMarginLeftAndBottom + mMarginTextLine + mYXAxisTextBounds.width(), index * yValue, width.toFloat(), index * yValue, mDashLinePaint)
if (index == 1) {
canvas.drawText("${maxTemp}°", mMarginLeftAndBottom, index * yValue + mYXAxisTextBounds.height() / 2, mYXAxisTextPaint)
}else if (index == 2) {
canvas.drawText("${minTemp}°", mMarginLeftAndBottom, index * yValue + mYXAxisTextBounds.height() / 2, mYXAxisTextPaint)
}
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val selectWidth = (width - mMarginLeftAndBottom - mMarginTextLine) / mMonthList.size
val selectIndex = (event.x - mMarginLeftAndBottom - mMarginTextLine) / selectWidth
when(event.action) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
if (selectIndex >= 0 && selectIndex < mMonthList.size) {
mCurrentDay = mMonthList[selectIndex.toInt()].date!!
}
invalidate()
}
}
return true
}
fun setDate(monthList: List<Month>) {
mMonthList = monthList.toMutableList()
maxTemp = monthList.maxOf { it.day?.temperature?.toInt() ?: 0 }
minTemp = monthList.minOf { it.day?.temperature?.toInt() ?: 0 }
invalidate()
}
}
图片效果