日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

android 坐标图绘制曲线,Android艺术之画一条平滑的曲线

發(fā)布時(shí)間:2025/3/19 71 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 坐标图绘制曲线,Android艺术之画一条平滑的曲线 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

說(shuō)的是曲線,其實(shí)想法是來(lái)自一個(gè)曲線圖的需求。圖表這種東西,項(xiàng)目開(kāi)發(fā)中也不少見(jiàn),大多情況找個(gè)通用的開(kāi)源框架改改就得了(老板們別打我),然而通用趕不上腦洞,要做交互和視覺(jué)比較特別的圖表時(shí),還是自己造一個(gè)輪子比較靠譜,這次要研究的就是一個(gè)優(yōu)雅而平滑的曲線怎么畫(huà)出來(lái)。

實(shí)現(xiàn)方法分析

曲線圖的責(zé)任概括起來(lái)就是把數(shù)據(jù)輸出為對(duì)應(yīng)的圖像,我們這次需求的目標(biāo)效果圖是這樣的:

坐標(biāo)軸和指示線等功能不是這篇文章的重點(diǎn),拋開(kāi)它們先不討論,這次研究的重點(diǎn)在于曲線的繪制,而說(shuō)到繪制曲線,最常用的參數(shù)曲線函數(shù)就是貝塞爾曲線。

二次貝塞爾曲線:

三次貝塞爾曲線:

關(guān)于貝塞爾曲線更詳細(xì)的內(nèi)容戳這里,Android也提供了繪制貝塞爾曲線的方法,方法參數(shù)就是對(duì)應(yīng)貝塞爾曲線的控制點(diǎn):

// 二次貝塞爾曲線

path.quadTo(auxiliaryX, auxiliaryY, endPointX, endPointY);

canvas.drawPath(path, paint);

// 三次貝塞爾曲線

path.cubicTo(auxiliaryOneX, auxiliaryOneY, auxiliaryTwoX, auxiliaryTwoY, endPointX, endPointY);

canvas.drawPath(path, paint);

仔細(xì)分析目標(biāo)效果圖,我們可以把曲線圖拆分為一段段短的曲線,每?jī)蓚€(gè)數(shù)據(jù)點(diǎn)之間用三次貝塞爾曲線來(lái)繪制,保證曲線經(jīng)過(guò)每個(gè)數(shù)據(jù)點(diǎn),如下圖所示,紅色和藍(lán)色的線段分別是兩條三次貝塞爾曲線。

這樣我們的任務(wù)就變?yōu)榇_定每一段三次貝塞爾曲線四個(gè)控制點(diǎn)P0、P1、P2和P3的位置,毫無(wú)疑問(wèn),數(shù)據(jù)點(diǎn)可以作為曲線的起點(diǎn)P0和終點(diǎn)P3,某個(gè)數(shù)值點(diǎn)既是前一條曲線的終點(diǎn)也是后一條曲線的起點(diǎn),而確定剩下的P1和P2的位置還需要一個(gè)重要的課堂知識(shí):

光滑曲線必定處處可導(dǎo)。 —— 《高中數(shù)學(xué)·必修一》

要保證整條曲線圖是光滑的,關(guān)鍵在于貝塞爾曲線的連接點(diǎn)也要保證光滑,而三次貝塞爾曲線中P0和P1的連線就是P0點(diǎn)的切線,P2和P3的連線是P3的切線,所以我們只要保證數(shù)據(jù)點(diǎn)左右控制點(diǎn)共線,就可以保證曲線在數(shù)據(jù)點(diǎn)處是可導(dǎo)光滑的。

如圖所示,A0、A3、B3分別是三個(gè)數(shù)據(jù)點(diǎn),A1、A2、B1、B2是控制點(diǎn),A0、A1、A2和A3構(gòu)建了一條三次貝塞爾曲線,B0、B1、B2和B3也構(gòu)建了一條三次貝塞爾曲線,當(dāng)A2和B1共線時(shí),A2和B1的連線就是A3點(diǎn)的切線,數(shù)據(jù)點(diǎn)A3點(diǎn)就是可導(dǎo)光滑的。之后,只要我們使控制點(diǎn)A2、B1連線的斜率和A3左右數(shù)據(jù)點(diǎn)A0、B3連線的斜率保持一致,就可以讓曲線的效果更自然。

設(shè)A0的坐標(biāo)為(A0X,A0Y),A3的坐標(biāo)為(A3X,A3Y),B3的坐標(biāo)為(B3X,B3Y),控制點(diǎn)A2、B1的坐標(biāo)計(jì)算方法如下:

A0和B3連線的斜率 k = (B3Y - A0Y) / (B3X - A0X)

常數(shù) b = A3Y - k * A3X

A2的X坐標(biāo) A2X = A3X - (A3X - A0X) * rate

A2的Y坐標(biāo) A2Y = k * A2X + b

B1的X坐標(biāo) B1X = A3X + (B3X - A3X) * rate

B1的Y坐標(biāo) B1Y = k * B1X + b

rate是一個(gè)(0, 0.5)區(qū)間內(nèi)的值,數(shù)值越大,數(shù)值點(diǎn)之間的曲線弧度越小。 除此以外,如果數(shù)值點(diǎn)是第一個(gè)點(diǎn)或者最后一個(gè)點(diǎn),可以把斜率k視為0,然后只計(jì)算左控制點(diǎn)或者有控制點(diǎn)。 我們只要把每個(gè)數(shù)值點(diǎn)左右的控制點(diǎn)坐標(biāo)計(jì)算出來(lái),然后畫(huà)出每一段曲線,就可以組成一個(gè)完整的圓滑曲線了。

代碼實(shí)現(xiàn)

基本原理就是這么多,還是貼代碼實(shí)際。先計(jì)算全部數(shù)據(jù)點(diǎn)的坐標(biāo),用mValuePointList保存起來(lái),max是圖表顯示的最大值,scaleX和scaleY分別是單位長(zhǎng)度

private fun calculateValuePoint(itemList: List, max: Float, scaleX: Float, scaleY: Float){

mValuePointList.clear()

for ((i, item) in itemList.withIndex()) {

val x = i * scaleX

val y = (max - item.value) * scaleY

mValuePointList.add(PointF(x, y))

}

}

然后計(jì)算控制點(diǎn)的坐標(biāo),用mControlPointList保存起來(lái)

private fun calculateControlPoint(pointList: List){

mControlPointList.clear()

if (pointList.size <= 1) {

return

}

for ((i, point) in pointList.withIndex()) {

when (i) {

0 -> {//第一項(xiàng)

//添加后控制點(diǎn)

val nextPoint = pointList[i + 1]

val controlX = point.x + (nextPoint.x - point.x) * SMOOTHNESS

val controlY = point.y

mControlPointList.add(PointF(controlX, controlY))

}

pointList.size - 1 -> {//最后一項(xiàng)

//添加前控制點(diǎn)

val lastPoint = pointList[i - 1]

val controlX = point.x - (point.x - lastPoint.x) * SMOOTHNESS

val controlY = point.y

mControlPointList.add(PointF(controlX, controlY))

}

else -> {//中間項(xiàng)

val lastPoint = pointList[i - 1]

val nextPoint = pointList[i + 1]

val k = (nextPoint.y - lastPoint.y) / (nextPoint.x - lastPoint.x)

val b = point.y - k * point.x

//添加前控制點(diǎn)

val lastControlX = point.x - (point.x - lastPoint.x) * SMOOTHNESS

val lastControlY = k * lastControlX + b

mControlPointList.add(PointF(lastControlX, lastControlY))

//添加后控制點(diǎn)

val nextControlX = point.x + (nextPoint.x - point.x) * SMOOTHNESS

val nextControlY = k * nextControlX + b

mControlPointList.add(PointF(nextControlX, nextControlY))

}

}

}

}

最后繪制曲線和數(shù)值

//連接各部分曲線

mPath.reset()

val firstPoint = pointList.first()

mPath.moveTo(firstPoint.x, height)

mPath.lineTo(firstPoint.x, firstPoint.y)

for (i in 0 until pointList.size * 2 step 2) {

val leftControlPoint = controlPointList[i]

val rightControlPoint = controlPointList[i + 1]

val rightPoint = pointList[i / 2 + 1]

mPath.cubicTo(leftControlPoint.x, leftControlPoint.y, rightControlPoint.x, rightControlPoint.y, rightPoint.x, rightPoint.y)

}

val lastPoint = pointList.last()

//填充漸變色

mPath.lineTo(lastPoint.x, height)

mPath.lineTo(firstPoint.x, height)

mPaint.alpha = 255

mPaint.style = Paint.Style.FILL

mPaint.shader = LinearGradient(0F, 0F, 0F, height, COLOR_GRAPH_FILL, null, Shader.TileMode.CLAMP)

canvas.drawPath(mPath, mPaint)

//繪制全部路徑

mPath.setLastPoint(lastPoint.x, height)

mPaint.strokeWidth = SIZE_GRAPH

mPaint.style = Paint.Style.STROKE

mPaint.shader = null

mPaint.color = COLOR_GRAPH

canvas.drawPath(mPath, mPaint)

for (i in 0..pointList.size()) {

val point = pointList[i]

//畫(huà)數(shù)值線

mPaint.color = COLOR_POINT

mPaint.alpha = 100

canvas.drawLine(point.x, point.y, point.x, height, mPaint)

//畫(huà)數(shù)值點(diǎn)

mPaint.style = Paint.Style.FILL

mPaint.alpha = 255

canvas.drawCircle(point.x, point.y, SIZE_POINT, mPaint)

}

OK!大功告成,最終效果圖:

剩下的刻度效果和滑動(dòng)效果并不是太復(fù)雜,有時(shí)間再寫(xiě)一篇吧,謝謝各位看官支持。

本文來(lái)自網(wǎng)易實(shí)踐者社區(qū),經(jīng)作者馮文浩授權(quán)發(fā)布。

總結(jié)

以上是生活随笔為你收集整理的android 坐标图绘制曲线,Android艺术之画一条平滑的曲线的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。