日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

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

發布時間:2025/3/19 Android 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 坐标图绘制曲线,Android艺术之画一条平滑的曲线 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

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

實現方法分析

曲線圖的責任概括起來就是把數據輸出為對應的圖像,我們這次需求的目標效果圖是這樣的:

坐標軸和指示線等功能不是這篇文章的重點,拋開它們先不討論,這次研究的重點在于曲線的繪制,而說到繪制曲線,最常用的參數曲線函數就是貝塞爾曲線。

二次貝塞爾曲線:

三次貝塞爾曲線:

關于貝塞爾曲線更詳細的內容戳這里,Android也提供了繪制貝塞爾曲線的方法,方法參數就是對應貝塞爾曲線的控制點:

// 二次貝塞爾曲線

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

canvas.drawPath(path, paint);

// 三次貝塞爾曲線

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

canvas.drawPath(path, paint);

仔細分析目標效果圖,我們可以把曲線圖拆分為一段段短的曲線,每兩個數據點之間用三次貝塞爾曲線來繪制,保證曲線經過每個數據點,如下圖所示,紅色和藍色的線段分別是兩條三次貝塞爾曲線。

這樣我們的任務就變為確定每一段三次貝塞爾曲線四個控制點P0、P1、P2和P3的位置,毫無疑問,數據點可以作為曲線的起點P0和終點P3,某個數值點既是前一條曲線的終點也是后一條曲線的起點,而確定剩下的P1和P2的位置還需要一個重要的課堂知識:

光滑曲線必定處處可導。 —— 《高中數學·必修一》

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

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

設A0的坐標為(A0X,A0Y),A3的坐標為(A3X,A3Y),B3的坐標為(B3X,B3Y),控制點A2、B1的坐標計算方法如下:

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

常數 b = A3Y - k * A3X

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

A2的Y坐標 A2Y = k * A2X + b

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

B1的Y坐標 B1Y = k * B1X + b

rate是一個(0, 0.5)區間內的值,數值越大,數值點之間的曲線弧度越小。 除此以外,如果數值點是第一個點或者最后一個點,可以把斜率k視為0,然后只計算左控制點或者有控制點。 我們只要把每個數值點左右的控制點坐標計算出來,然后畫出每一段曲線,就可以組成一個完整的圓滑曲線了。

代碼實現

基本原理就是這么多,還是貼代碼實際。先計算全部數據點的坐標,用mValuePointList保存起來,max是圖表顯示的最大值,scaleX和scaleY分別是單位長度

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))

}

}

然后計算控制點的坐標,用mControlPointList保存起來

private fun calculateControlPoint(pointList: List){

mControlPointList.clear()

if (pointList.size <= 1) {

return

}

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

when (i) {

0 -> {//第一項

//添加后控制點

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 -> {//最后一項

//添加前控制點

val lastPoint = pointList[i - 1]

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

val controlY = point.y

mControlPointList.add(PointF(controlX, controlY))

}

else -> {//中間項

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

//添加前控制點

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

val lastControlY = k * lastControlX + b

mControlPointList.add(PointF(lastControlX, lastControlY))

//添加后控制點

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

val nextControlY = k * nextControlX + b

mControlPointList.add(PointF(nextControlX, nextControlY))

}

}

}

}

最后繪制曲線和數值

//連接各部分曲線

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]

//畫數值線

mPaint.color = COLOR_POINT

mPaint.alpha = 100

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

//畫數值點

mPaint.style = Paint.Style.FILL

mPaint.alpha = 255

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

}

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

剩下的刻度效果和滑動效果并不是太復雜,有時間再寫一篇吧,謝謝各位看官支持。

本文來自網易實踐者社區,經作者馮文浩授權發布。

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。