文章目录
背景
最近LoL s10全球总决赛正在如火如荼的进行,作为一名老玩家,笔者在观看比赛的同时,注意到每场比赛的经济对比图实时的展示,并且红蓝双方的经济曲线对比呈现不同的颜色,于是笔者来了兴趣,能否在安卓上自定义控件实现相似的功能呢?说干就干,经过笔者不懈的努力,终于实现了相似的功能。
实现效果
需求分析
LoL实时仅仅对比图需实现以下功能:按照阵营分为红蓝双方,蓝方在基准线以上,红方在基准线一下,曲线显示双方的经济差,当蓝方经济领先,曲线在基线上方,且曲线为蓝色,当红方经济领先,在基线一下,变为红色。当用户选择某个时间段,能显示出当前时间段的经济坐标。
实现思路
首先,我们需要将数据封装成对应的x,y 坐标的集合,,然后在绘制x轴,y轴,接着将点与点之间用贝塞尔曲线联合。如何实现在两点之间绘制一条三阶贝塞尔曲线,并且实现基线以上是蓝色,基线一下是红色,成为难点,这里我们的思路是分段绘制的思路,即计算计算出两点连线与基线的焦点,然后分段绘制,这里在分段绘制时,采用三阶贝塞尔曲线。如何控制锚点,使其连线看起来更加平滑成为难题。
如果读者对三阶贝塞尔曲线不甚了解,可以去查看相关文档。
经过笔者多番调试,终于调出较为满意的效果。
代码实现
绘制x轴
// 画x轴
private fun drawXAxis(canvas: Canvas) {
val rate = returnRate()
val y = layoutRect.bottom - textHeight
mTimeTextPaint.textAlign = Paint.Align.CENTER
mTimeTextPaint.strokeWidth = 2f
mTimeTextPaint.style = Paint.Style.FILL
val showNum = currentDescArray.size
for (i in 0 until showNum) {
val x =
(rate * i) + lineStartX.toFloat() + tempPaddingStartX.toFloat()
canvas.drawText(currentDescArray[i], x, y, mTimeTextPaint)
}
}
绘制y轴
// 画Y轴及线
private fun drawYAxis(canvas: Canvas) {
if (txtYArray.isEmpty()) return
mPaint.style = Paint.Style.FILL
mPaint.strokeWidth = 5f
mPaint.color = Color.BLACK
val keyValueY = (distanceH / (lineNumY + 1)).toFloat()
var yValue: Float //记录y的值
var showStr: String?
for (i in 0 until lineNumY) {
yValue = keyValueY * (i + 2) - subtractPadding
if (txtYArray[i] > 0) {
showStr = (txtYArray[i] / 1000).toString() + "k"
mPaint.color = resources.getColor(R.color.color_team_blue)
canvas.drawText(
showStr,
(lineStartX - showPadding).toFloat(),
yValue + 0.25f * textHeight,
mPaint
)
} else if (txtYArray[i] == 0F) {
showStr = txtYArray[i].toString()
mPaint.color = resources.getColor(R.color.color_999)
canvas.drawText(
showStr,
(lineStartX - showPadding).toFloat(),
yValue + 0.25f * textHeight,
mPaint
)
} else {
showStr = (Math.abs(txtYArray[i] / 1000)).toString() + "k"
mPaint.color = resources.getColor(R.color.color_team_red)
canvas.drawText(
showStr,
(lineStartX - showPadding).toFloat(),
yValue + 0.25f * textHeight,
mPaint
)
}
canvas.drawLine(
(lineStartX - showPadding).toFloat() + yleftWidth / 2 + 20,
yValue,
(distanceW - lineStartX + showPadding).toFloat(),
yValue,
mLinePaint
)
if (i == 0) {
topLineY = yValue
}
if (i == lineNumY - 1) {
btmLinY = yValue
}
}
}
绘制贝塞尔曲线
private fun drawBeizer(canvas: Canvas) {
if (pointList.isNotEmpty()) {
mPaint.style = Paint.Style.STROKE
mPaint.strokeWidth = 8F
mPaint.color = resources.getColor(R.color.color_team_red)
var startp: Point
var endp: Point
for (i in 0 until pointList.size - 1) {
startp = pointList[i]
endp = pointList[i + 1]
//两个点同时在基线上
if (startp.py < baseZeroy && endp.py < baseZeroy) {
mPaint.color = resources.getColor(R.color.color_team_blue)
val wt = (startp.px + endp.px) / 2
path.reset()
path.moveTo(startp.px, startp.py)
path.cubicTo(wt, startp.py, wt, endp.py, endp.px, endp.py)
canvas.drawPath(path, mPaint)
}
//两个点同时在基线下
else if (startp.py > baseZeroy && endp.py > baseZeroy) {
mPaint.color = resources.getColor(R.color.color_team_red)
val wt = (startp.px + endp.px) / 2
path.reset()
path.moveTo(startp.px, startp.py)
path.cubicTo(wt, startp.py, wt, endp.py, endp.px, endp.py)
canvas.drawPath(path, mPaint)
}
//一个点在基线上,一个点在基线下,此时分成两段去绘制
else {
//当前基线的与该线段较短的x坐标
var baseZeroX = getZeroX(startp.px, endp.px, startp.py, endp.py)
//起点在基线一下
if (startp.py > baseZeroy) {
mPaint.color = resources.getColor(R.color.color_team_red)
val wtleft = (startp.px + baseZeroX) / 2
val htLeft= (startp.py + baseZeroy) / 2
var leftRateX=(baseZeroX-startp.px)/4
var leftRateY=(baseZeroy-startp.py)/4
path.reset()
path.moveTo(startp.px, startp.py)
path.cubicTo(wtleft, startp.py, wtleft+leftRateX, htLeft-leftRateY, baseZeroX, baseZeroy)
canvas.drawPath(path, mPaint)
mPaint.color = resources.getColor(R.color.color_team_blue)
val wtRight = (baseZeroX + endp.px) / 2
val htRight= (baseZeroy + endp.py) / 2
var rightRateX=(endp.px-baseZeroX)/4
var rightRateY=(endp.py-baseZeroy)/4
path.reset()
path.moveTo(baseZeroX, baseZeroy) //为了保持裁点时曲线的平滑
path.cubicTo(wtRight-rightRateX, htRight+rightRateY, wtRight, endp.py, endp.px, endp.py)
canvas.drawPath(path, mPaint)
} else {
mPaint.color = resources.getColor(R.color.color_team_blue)
val wtleft = (startp.px + baseZeroX) / 2
val htLeft= (startp.py + baseZeroy) / 2
var leftRateX=(baseZeroX-startp.px)/4
var leftRateY=(baseZeroy-startp.py)/4
path.reset()
path.moveTo(startp.px, startp.py)
path.cubicTo(wtleft, startp.py, wtleft+leftRateX, htLeft-leftRateY, baseZeroX, baseZeroy)
canvas.drawPath(path, mPaint)
mPaint.color = resources.getColor(R.color.color_team_red)
val wtRight = (baseZeroX + endp.px) / 2
val htRight= (baseZeroy + endp.py) / 2
var rightRateX=(endp.px-baseZeroX)/4
var rightRateY=(endp.py-baseZeroy)/4
path.reset()
path.moveTo(baseZeroX, baseZeroy)
path.cubicTo(wtRight-rightRateX, htRight+rightRateY, wtRight, endp.py, endp.px, endp.py)
canvas.drawPath(path, mPaint)
}
}
}
}
}
绘制触摸效果
private fun drawTouch(canvas: Canvas) {
if (pointList.size > 0 && linex >= 0 && linex < pointList.size) {
var currentPoint = pointList[linex]
//基线一下
if (currentPoint.py >= baseZeroy) {
mValueLinePaint?.color = resources.getColor(R.color.color_team_red)
canvas.drawCircle(
pointList[linex].px,
pointList[linex].py,
14F,
mValueLinePaint!!
)// 小圆
mValueLinePaint?.isAntiAlias = true
mPointLine.color = resources.getColor(R.color.color_team_red)
canvas.drawLine(
pointList[linex].px,
topLineY,
pointList[linex].px,
btmLinY,
mPointLine
)
} else {
mValueLinePaint?.color = resources.getColor(R.color.color_team_blue)
canvas.drawCircle(
pointList[linex].px,
pointList[linex].py,
14F,
mValueLinePaint!!
)// 小圆
mValueLinePaint?.isAntiAlias = true
mPointLine.color = resources.getColor(R.color.color_team_blue)
canvas.drawLine(
pointList[linex].px,
topLineY,
pointList[linex].px,
btmLinY,
mPointLine
)
}
if (listener == null) return
dataLists[linex].avg.toString()
listener?.onPointSelect(
buleTeam,
redTeam,
currentPoint.px,
currentPoint.py,
dataLists[linex].timeStamp,
dataLists[linex].avg.toString(),
distanceW
)
}
}
小结
附上GitHub的地址,大家如果需要可以进去clone下来,仔细研读,如果觉得还行,能对你有所帮助,别忘了给我个star哦