直接上代码
class RainProbabilityView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val viewTopTextHeight = PxUtils.dip2px(60f)
private val viewBottomTextHeight = PxUtils.dip2px(30f)
private val itemWidth = PxUtils.dip2px(60f)
private var viewHeight = 0
private var viewBottom = 0
private val itemList = arrayListOf<ItemData>()
private val viewPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val dashPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val timePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val valuePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val probabilityPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG)
private val viewPath = Path()
private val borderLinePath = Path()
private val viewColor = Color.parseColor("#FF00A3FF")
private val dashColor = Color.parseColor("#FFB2DDFF")
private val valueColor = Color.parseColor("#FF9E9E9E")
private val timeColor = Color.parseColor("#FF9E9E9E")
private val timeBoldColor = Color.parseColor("#FF333333")
private val lineColor = Color.parseColor("#FFF5F5F5")
private val valueTextSize = PxUtils.dip2px(12f).toFloat()
private val timeTextSize = PxUtils.dip2px(14f).toFloat()
init {
genTestData()
viewPaint.color = viewColor
viewPaint.style = Paint.Style.FILL
viewPaint.isDither = true
viewPaint.strokeWidth = 3f
viewPaint.pathEffect = CornerPathEffect(25f)
dashPaint.color = dashColor
dashPaint.strokeWidth = 1f
dashPaint.pathEffect = DashPathEffect(floatArrayOf(10f, 5f), 0f)
linePaint.color = lineColor
linePaint.style = Paint.Style.STROKE
linePaint.strokeWidth = 1f
linePaint.pathEffect = CornerPathEffect(25f)
timePaint.textAlign = Paint.Align.CENTER
timePaint.color = timeColor
timePaint.textSize = timeTextSize
timePaint.style = Paint.Style.FILL
valuePaint.textAlign = Paint.Align.CENTER
valuePaint.color = valueColor
valuePaint.textSize = valueTextSize
valuePaint.style = Paint.Style.FILL
probabilityPaint.textAlign = Paint.Align.CENTER
probabilityPaint.color = viewColor
probabilityPaint.textSize = valueTextSize
probabilityPaint.style = Paint.Style.FILL
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)
val width = itemWidth * itemList.size
setMeasuredDimension(width, heightSpecSize)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
this.viewHeight = h - viewTopTextHeight - viewBottomTextHeight
this.viewBottom = viewTopTextHeight + viewHeight
var maxValue = 0.0
itemList.forEach { item ->
maxValue = max(item.value, maxValue)
}
itemList.forEachIndexed { index, itemData ->
val x = itemWidth * index + itemWidth / 2
val y =
viewTopTextHeight + (viewHeight - (itemData.value * 1.0 / maxValue) * viewHeight)
itemData.topPoint = Point(x, y.toInt())
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
drawPath(canvas)
drawDash(canvas)
drawText(canvas)
}
private fun drawText(canvas: Canvas?) {
itemList.forEachIndexed { index, itemData ->
val x = itemData.topPoint.x.toFloat()
val y = itemData.topPoint.y.toFloat()
val probabilityY = y - PxUtils.dip2px(15f).toFloat()
val valueY = probabilityY - PxUtils.dip2px(15f).toFloat()
val timeY = viewBottom + PxUtils.dip2px(20f).toFloat()
canvas?.drawText("${itemData.value}mm", x, valueY, valuePaint)
canvas?.drawText("${itemData.probability}%", x, probabilityY, probabilityPaint)
canvas?.drawText(itemData.time, x, timeY, timePaint)
}
}
private fun drawDash(canvas: Canvas?) {
itemList.forEachIndexed { index, itemData ->
val x = itemData.topPoint.x.toFloat()
val y = itemData.topPoint.y.toFloat() - PxUtils.dip2px(10f).toFloat()
canvas?.drawLine(x, y, x, viewBottom.toFloat(), dashPaint)
canvas?.drawCircle(x, viewBottom.toFloat(), PxUtils.dip2px(4f).toFloat(), dashPaint)
}
}
private fun drawPath(canvas: Canvas?) {
viewPath.reset()
itemList.forEachIndexed { index, itemData ->
val x = itemData.topPoint.x.toFloat()
val y = itemData.topPoint.y.toFloat()
if (index == 0) {
borderLinePath.reset()
borderLinePath.moveTo(x - itemWidth / 2 + 2, 0f)
borderLinePath.lineTo(x - itemWidth / 2 + 2, viewBottom.toFloat() - 2)
borderLinePath.lineTo(x, viewBottom.toFloat() - 2)
canvas!!.drawPath(borderLinePath, linePaint)
viewPath.moveTo(x - itemWidth / 2, viewBottom.toFloat())
viewPath.lineTo(x - itemWidth / 2, (viewBottom - (viewBottom - y) * 0.7).toFloat())
viewPath.lineTo(x, y)
} else {
viewPath.lineTo(x, y)
}
if (index == itemList.size - 1) {
borderLinePath.reset()
borderLinePath.moveTo(x + itemWidth / 2 - 2, 0f)
borderLinePath.lineTo(x + itemWidth / 2 - 2, viewBottom.toFloat() - 2)
borderLinePath.lineTo(x, viewBottom.toFloat() - 2)
canvas!!.drawPath(borderLinePath, linePaint)
viewPath.lineTo(x + itemWidth / 2, (viewBottom - (viewBottom - y) * 0.7).toFloat())
viewPath.lineTo(x + itemWidth / 2, viewBottom.toFloat())
viewPath.close()
}
}
canvas!!.drawPath(viewPath, viewPaint)
}
fun genTestData() {
itemList.clear()
val rain = arrayOf(
12.0,
6.0,
4.0,
18.0,
18.0,
4.0,
10.0,
9.0,
4.0,
10.0,
20.0,
10.0,
12.0,
6.0,
4.0,
18.0,
18.0,
12.0,
10.0,
0.0,
0.0,
0.0,
0.0,
6.0
)
rain.forEachIndexed { index, d ->
itemList.add(ItemData("${index}时", d, 80, false, Point()))
}
}
}
class ItemData(
val time: String,
val value: Double,
val probability: Int,
val bold: Boolean,
var topPoint: Point
)
PxUtils 工具类
public class PxUtils {
private static float sDensity = 1.0f;
static {
Resources resources = Resources.getSystem();
if (resources != null) {
sDensity = resources.getDisplayMetrics().density;
}
}
public static int dip2px(float dipVlue) {
return (int) (dipVlue * sDensity + 0.5f);
}
public static int px2dip(float pxValue) {
final float scale = sDensity;
return (int) (pxValue / scale + 0.5f);
}
}
xml布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.aqian.tech.test.weather.RainProbabilityView
android:id="@+id/rain_probability_view"
android:layout_width="2000dp"
android:layout_height="150dp"
android:layout_marginHorizontal="20dp" />
</LinearLayout>
</HorizontalScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>