安卓绘制温度趋势图

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()
    }

}

图片效果
在这里插入图片描述

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值