自定义View——可横向滑动的折线图

问题:如何画一个显示一天中24h气温变化的折线图,要求折线图能横向滚动。
思路: 先自定义一个View,这个View中只包含两个控件,一个圆,和一个显示温度的TextView。代码如下
package com.example.viewtest.linechart

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Point
import android.util.AttributeSet
import android.util.Log
import android.view.View
import kotlin.math.max

class Temperature: View{
    constructor(context: Context):super(context) {
        init()
    }

    constructor(context: Context, attributeSet: AttributeSet):super(context, attributeSet){
        init()
    }

    private var pointX = 0F //圆的横坐标
    private var pointY = 0F //圆的纵坐标
    private var maxTem = 0  //一天中气温的最大值
    private var minTem = 0
    private var tem = 0     //当前温度
    private val radius = 6F //圆的半径
    private lateinit var paint: Paint
    private lateinit var textPaint: Paint

    fun init() {    //初始化两个Paint
        paint = Paint()
        paint.color = Color.BLUE
        textPaint = Paint()
        textPaint.color = Color.WHITE
        textPaint.textSize = 25F
        textPaint.textAlign = Paint.Align.CENTER
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        drawPoint(canvas)
        drawText(canvas)
    }

    fun drawPoint(canvas: Canvas?) {    //画圆
        val height = height - 100   // - 100 是为了让点的位置上移,更加美观
        val width = width/2F
        val pointHeight = height - height * ((tem - minTem)*1.0F / (maxTem - minTem)) + 50  //根据当前温度的值设置高度,温度越高,值越小,圆的位置也就越往上
        pointX = width
        pointY = pointHeight
        canvas?.drawCircle(width, pointHeight, radius, paint)

    }

    fun drawText(canvas: Canvas?) {     //画文字
        val content = "${tem}℃"
        val height = height - 100
        val width = width/2
        val textHeight = height-height * ((tem - minTem)*1.0F / (maxTem - minTem)) + 25
        canvas?.drawText(content, width.toFloat(), textHeight, textPaint)

    }

    fun setMaxTem(maxTem: Int) {    //初始化相关值
        this.maxTem = maxTem
    }

    fun setMinTem(minTem: Int) {
        this.minTem = minTem
    }

    fun setTem(tem: Int) {
        this.tem = tem
        invalidate()
    }

    fun getPointX(): Float {    //获取圆的横坐标,后面画折线要用到
        return pointX
    }

    fun getPointY(): Float {
        return pointY
    }
}
接着自定义一个ViewGroup, 继承LinearLayout用于显示时间,天气状况,气温。代码如下
package com.example.viewtest.linechart

import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.res.ResourcesCompat.getDrawable
import com.example.viewtest.R

class WeatherItem(context: Context):LinearLayout(context) {
    private var date: TextView  //时间
    private var tem:Temperature //温度
    private var weatherCon:TextView   //天气状况

    init {
        val view = LayoutInflater.from(context).inflate(R.layout.linechart_item, null)
        date = view.findViewById(R.id.date)
        tem = view.findViewById(R.id.tem)
        weatherCon = view.findViewById(R.id.wind)   //得到相应控件的实例
        view.layoutParams = LayoutParams(       //设置View的宽高属性
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        addView(view)   //将View添加到ViewGroup中
    }


    fun setDate(dateData: String) { //设置相关数据
       date.text = dateData
    }

    fun setMaxTem(max: Int) {
        tem.setMaxTem(max)
    }

    fun setMinTem(min: Int) {
        tem.setMinTem(min)
    }

    fun setTem(tem: Int) {
        this.tem.setTem(tem)
    }

    fun setWeatherCon(windData: String) {
        weatherCon.text = windData
    }
}
最后自定义一个ViewGroup, 继承HorizontalScrollView。以实现横向滚动的功能,代码如下
package com.example.viewtest.linechart

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.TextView
import com.example.viewtest.R
import com.example.viewtest.linechart.model.WeatherData

class WeatherView: HorizontalScrollView {
    constructor(context: Context):super(context) {
        init()
    }

    constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet) {
        init()
    }

    private lateinit var paint: Paint

    fun init() {    //初始化Paint
        paint = Paint()
        paint.color = Color.RED
        paint.strokeWidth = 6f
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        if (childCount > 0) {
            val root = getChildAt(0) as LinearLayout    // 获取WeatherView的子视图
            for (i in 0..5) {       // 循环,画折线
                val tem1 = root.getChildAt(i)       // 获取LinearLayout子视图WeatherItem
                val temView1 = tem1.findViewById<Temperature>(R.id.tem)
                val tem2 = root.getChildAt(i + 1)
                val temView2 = tem2.findViewById<Temperature>(R.id.tem)
                val date = tem1.findViewById<TextView>(R.id.date)   // 实例化控件
                val dateHeight = date.height
                val pointX1 = temView1.getPointX() + tem1.width * i
                val pointY1 = temView1.getPointY() + dateHeight
                val pointX2 = temView2.getPointX() + tem1.width * (i + 1)
                val pointY2 = temView2.getPointY() + dateHeight     // 获取折线两个端点的值
                canvas?.drawLine(pointX1, pointY1, pointX2, pointY2, paint)
            }
        }
    }

    fun setData(weatherData: List<WeatherData>) {   // 设置初始值
        val window = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val width = window.defaultDisplay.width     // 获取屏幕宽度
        val linearLayout = LinearLayout(context)    // 由于HorizontalScrollView只能有一个子View,所以需要把WeatherItem加到LinearLayout里
        linearLayout.layoutParams = LayoutParams(   // 设置宽高
            ViewGroup.LayoutParams.MATCH_PARENT,
            ViewGroup.LayoutParams.MATCH_PARENT
        )
        linearLayout.orientation = LinearLayout.HORIZONTAL
        for (i in 0..6) {       // 添加WeatherItem
            val item = WeatherItem(context)
            val data = weatherData[i]
            item.setDate(data.date)
            item.setMaxTem(data.maxTem)
            item.setMinTem(data.minTem)
            item.setTem(data.tem)
            item.setWeatherCon(data.windData)
            item.layoutParams = LinearLayout.LayoutParams(
                width / 5,
                LinearLayout.LayoutParams.MATCH_PARENT
            )
            linearLayout.addView(item)
        }
        addView(linearLayout)   // addView之后会刷新视图
    }
}

效果图如下。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_47885879/article/details/107985777