问题:如何画一个显示一天中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之后会刷新视图
}
}
效果图如下。