生活随笔
收集整理的這篇文章主要介紹了
Jetpack-Compose-自定义绘制
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
- 上節課我們簡單的利用了一下自定義裁剪和自定義就能玩出如下簡單案例,效果不錯。這節課咋們來看看Compose自定義繪制能不能花里胡哨。
一、Compose自定義
- 自定義,一個應用的可創造性往往離不開人們的千奇百怪想象和用戶變化萬千的需求。自定義就是提供了移動端的可創造性。如果Compose沒有了自定義那就沒有了創造性。自定義不熟悉的看我之前的博客。
????1.Android自定義-曲線漸變填充
????2.Android自定義-手勢縮放折線圖
????3.Android自定義-手勢滑動縮放漸變填充曲線折線圖表
????4.藝術在于繪制
????5.繪制相關API…
????
二、繪制開始
- 在原生中寫過的內容,曲線和的漸變填充其實很簡單了,簡單的數學加減乘除計算加個塞貝爾曲線而已。接下來咋們看看Compose中如何搞定它。
1.Compose中繪制組件
- Compose中Canvas作為繪制組件。基本的結構如下,上節課也講過:
Canvas(modifier = Modifier.fillMaxWidth().fillMaxHeight(),) {drawIntoCanvas { canvas ->//內部提供size來獲取自身寬高}
2.擺正坐標系
- 默認的坐標系Android,Flutter,SwiftUI都在左上角咋們試一試Compose坐標系。我們在(100,100)處繪制一個半徑為100的紅色圓圈,如果出現在屏幕的左上方和下面一致就說明一致。
Canvas(modifier
= Modifier
.fillMaxWidth().fillMaxHeight(),) {drawIntoCanvas
{ canvas
->val paint
=Paint()paint
.style
=PaintingStyle
.Fillpaint
.color
=Color
.Greencanvas
.drawCircle(Offset(100f,100f),100f,paint
)}}
效果如下ok坐標系一致,我們繪制開始。
- 首先擺正坐標系位置目前位置是前面的變為后面的。
canvas.scale(1f,-1f)+canvas.translate(0f,-height)或者canvas.translate(0f,-height)+canvas.scale(1f,-1f)搞定。不懂的看我自定義的博客
下面代碼如果正確那么圓圈應該在左下角
Canvas(modifier
= Modifier
.fillMaxWidth().fillMaxHeight(),) {drawIntoCanvas
{ canvas
->val paint
= Paint()paint
.style
= PaintingStyle
.Fillpaint
.color
= Color
.Greencanvas
.translate(1f, -1f)canvas
.drawCircle(Offset(100f, 100f), 100f, paint
)}}
看完效果是不是略顯尷尬,剛看到其實我是懷疑模擬器有問題…我們來看真機器華為mate30 pro。完全正確有沒有。所以坑挺多的,一不小心就懷疑人生了。
3、繪制平行X軸的橫線
- 首先看原圖左右兩邊都有空格,且坐標系為了方便可以底部和左邊留有余地。
@Preview(name
= "canvas")
@Composable
fun MyCureView(mainActions
: MainActions
) {val marginToLeft
= 180fval marginToBootom
= 240fCanvas(modifier
= Modifier
.fillMaxWidth().fillMaxHeight(),) {drawIntoCanvas
{ canvas
->val paint
= Paint()paint
.style
= PaintingStyle
.Fillpaint
.color
= Color
.Greencanvas
.translate(0f,size
.height
)canvas
.scale(1f, -1f)canvas
.translate(marginToLeft
,marginToBootom
)canvas
.drawCircle(Offset(100f, 100f), 100f, paint
)}}}
后面如果不夠再做調整。我們來繪制一條橫線。
val line_paint
= Paint()line_paint
.strokeWidth
= 2fline_paint
.style
= PaintingStyle
.Strokeline_paint
.color
= Color( 188, 188, 188,100)x_scaleWidth
= (size
.width
- marginToLeft
- 80f)val onePath
=Path()onePath
.lineTo(x_scaleWidth
,0f)canvas
.drawPath(onePath
,line_paint
)
我們看到總共四條線循環遍歷了。
private fun DrawScope
.drawXLine(x_scaleWidth
: Float
,marginToLeft
: Float
,grid_width
: Float
,canvas
: androidx
.compose
.ui
.graphics
.Canvas
) {var x_scaleWidth1
= x_scaleWidth
var grid_width1
= grid_width
val line_paint
= Paint()line_paint
.strokeWidth
= 2fline_paint
.style
= PaintingStyle
.Strokeline_paint
.color
= Color(188, 188, 188, 100)x_scaleWidth1
= (size
.width
- marginToLeft
- 80f)grid_width1
= x_scaleWidth1
/ 6val onePath
= Path()onePath
.lineTo(x_scaleWidth1
, 0f)canvas
.drawPath(onePath
, line_paint
)canvas
.save()(0 until
3).forEach { index
->canvas
.translate(0f, grid_width1
- 40f)canvas
.drawPath(onePath
, line_paint
)}canvas
.restore()
}
當然了手機看著很清晰,圖片模糊體諒一下太大了不好處理。
4、繪制文字
- 繪制文字讓我費盡了腦汁。首先是在import androidx.compose.ui.graphics.*一類庫下面找了好久好久。由于太天真放棄了一個類一個類的尋找。40多個類讓我一個個看么,最后放棄這個法子,然后一頓操作在Google官網各種姿勢各種無語呀。上一篇博客半夜有哥們回我萬事有google搜索引擎,給我截圖發在了評論區,好家伙今早一頓Google搜索有切只有一個Google專家寫過一個案例。前無古人后無來者那種。就那一篇,我開心的像個孩子,拿起一頓CV,我特么的傻了,我編譯器提示不能解析到nativeCanvas,接下來靜下來分析了一波的卻在import androidx.compose.ui.graphics.*找到了所在地如下截圖。nativaCanvas就是graphics轉為原生Canvas。但是我的項目為嘛引用不了呢?而且Google專家沒有提供相關的項目。最后我詢問了幾個大佬未果,最終我懷疑到gradle版本頭上,是不是這個常出問題的東西搞的。相信很多寫Flutter又沒接觸過Android的伙伴們深受其害,苦不堪言。將Gradle版本跟新到最新版本即可解決。
Gradle沒問題版本gradle-7.0-milestone-2-bin.zip
????????????????????????????
之前用的
6.8版本出問題。
最新
distributionUrl
=https\
://services
.gradle
.org
/distributions
/gradle
-7.0-milestone
-2-bin
.zip
????????????????????????????
- canvas.nativeCanvas來進行原生Canvas獲取
接著繪制文字即可
1.文字繪制通過paint.getTextBundl不知道的看我之前的自定義文章。
2.x軸的長度和個數知道那么一個的寬度也就能知道x_scaleWidth / 6
3.
fun DrawScope
.drawTextDownX(x_scaleWidth
: Float
,marginToLeft
: Float
,grid_width
: Float
,canvas
: androidx
.compose
.ui
.graphics
.Canvas
,paint
: Paint
) {var x_scaleWidth1
= x_scaleWidth
var grid_width1
= grid_widthx_scaleWidth1
= (size
.width
- marginToLeft
- 80f)grid_width1
= x_scaleWidth1
/ 6val text_paint
= android
.graphics
.Paint()text_paint
.strokeWidth
= 2ftext_paint
.style
= android
.graphics
.Paint
.Style
.STROKEtext_paint
.color
= android
.graphics
.Color
.argb(100, 111, 111, 111)text_paint
.textSize
= 19fval rectText
= Rect()canvas
.save()canvas
.scale(1f, -1f)(0 until
7).forEach { index
->if (index
> 0) {Log
.e("weima?", "MyCureView: " + grid_width1
)canvas
.nativeCanvas
.translate(grid_width1
, 0f)}val strTx
= "11.${11 + index}"text_paint
.getTextBounds(strTx
, 0, strTx
.length
, rectText
)canvas
.nativeCanvas
.drawText(strTx
,-rectText
.width().toFloat() / 2,rectText
.height().toFloat() * 2.5f,text_paint
)}canvas
.restore()
}
同樣的繪制Y軸左邊的文字。
private fun DrawScope
.drawTextOfYLeft(x_scaleWidth
: Float
,marginToLeft
: Float
,grid_width
: Float
,canvas
: androidx
.compose
.ui
.graphics
.Canvas
) {var x_scaleWidth1
= x_scaleWidth
var grid_width1
= grid_width
val text_paint
= android
.graphics
.Paint()text_paint
.strokeWidth
= 2ftext_paint
.style
= android
.graphics
.Paint
.Style
.STROKEtext_paint
.color
= android
.graphics
.Color
.argb(100, 111, 111, 111)text_paint
.textSize
= 19fx_scaleWidth1
= (size
.width
- marginToLeft
- 80f)grid_width1
= x_scaleWidth1
/ 6val rectText
= Rect()canvas
.save()(0 until
4).forEach { index
->if (index
> 0) {canvas
.translate(0f, grid_width1
- 40f)}var strTx
= ""if (index
== 0) {strTx
= "${index}"} else if (index
== 1) {strTx
= "${500}"} else if (index
== 2) {strTx
= "1k"} else {strTx
= "1.5k"}canvas
.save()canvas
.scale(1f, -1f)text_paint
.getTextBounds(strTx
, 0, strTx
.length
, rectText
)canvas
.nativeCanvas
.drawText(strTx
,-rectText
.width().toFloat() - 42f,rectText
.height().toFloat() / 2,text_paint
)canvas
.restore()}canvas
.restore()
}
5、繪制曲線
當y1y_1y1?<y2y_2y2?如上圖1.求出中點坐標x軸下部分控制點x+40px,上部分x-40px,y軸也可以調整來搞搞平滑度下部分控制點y-40x,上部分y+40。
1.獲取中點的坐標(X中X_中X中?、Y中Y_中Y中?)= ((x1x_1x1?+x2x_2x2?)/2、(y1y_1y1?+y2y_2y2?)/2)
2.x1x_1x1?到X中X_中X中?之間的坐標=((x1x_1x1?+x中x_中x中?)/2、(y1y_1y1?+y中y_中y中?)/2)
3.x中x_中x中?到X2X_2X2?之間的坐標=((x中x_中x中?+x2x_2x2?)/2、(y中y_中y中?+y2y_2y2?)/2)
當y1y_1y1?>y2y_2y2?如上圖2.求出中點坐標x軸上部分+40px,下部分x-40px,y軸也可以調整,y軸也可以調整來搞搞平滑度上部分控制點y+40x,下部分y-40。
1.獲取中點的坐標(X中X_中X中?、Y中Y_中Y中?)= ((x1x_1x1?+x2x_2x2?)/2、(y1y_1y1?+y2y_2y2?)/2)
2.x1x_1x1?到X中X_中X中?之間的坐標=((x1x_1x1?+x中x_中x中?)/2、(y1y_1y1?+y中y_中y中?)/2)
3.x中x_中x中?到X2X_2X2?之間的坐標=((x中x_中x中?+x2x_2x2?)/2、(y中y_中y中?+y2y_2y2?)/2)
private fun DrawScope
.drawCubtoCircle(x_scaleWidth
: Float
,marginToLeft
: Float
,grid_width
: Float
,dataList
: ArrayList
<Int
>,canvas
: androidx
.compose
.ui
.graphics
.Canvas
) {var x_scaleWidth1
= x_scaleWidth
var grid_width1
= grid_widthx_scaleWidth1
= (size
.width
- marginToLeft
- 80f)grid_width1
= x_scaleWidth1
/ 6val text_paint
= android
.graphics
.Paint()text_paint
.strokeWidth
= 2ftext_paint
.style
= android
.graphics
.Paint
.Style
.FILLtext_paint
.color
= android
.graphics
.Color
.argb(100, 111, 111, 111)val caves_path
= android
.graphics
.Path()val danweiY
= (grid_width1
- 40) / 500val danweiX
= (grid_width1
)val linearGradient
= LinearGradient(0f, 1500 * danweiY
,0f,0f,android
.graphics
.Color
.argb(255, 229, 160, 144),android
.graphics
.Color
.argb(255, 251, 244, 240),Shader
.TileMode
.CLAMP
)text_paint
.shader
= linearGradient
for (index
in 0 until dataList
.size
- 1) {val xMoveDistance
= 20val yMoveDistance
= 40if (dataList
[index
] == dataList
[index
+ 1]) {caves_path
.lineTo(danweiX
* (index
+ 1), 0f)} else if (dataList
[index
] < dataList
[index
+ 1]) {val centerX
= (grid_width1
* index
+ grid_width1
* (1 + index
)) / 2val centerY
=(dataList
[index
].toFloat() * danweiY
+ dataList
[index
+ 1].toFloat() * danweiY
) / 2val controX0
= (grid_width1
* index
+ centerX
) / 2val controY0
= (dataList
[index
].toFloat() * danweiY
+ centerY
) / 2val controX1
= (centerX
+ grid_width1
* (1 + index
)) / 2val controY1
= (centerY
+ dataList
[index
+ 1].toFloat() * danweiY
) / 2caves_path
.cubicTo(controX0
+ xMoveDistance
,controY0
- yMoveDistance
,controX1
- xMoveDistance
,controY1
+ yMoveDistance
,grid_width1
* (1 + index
),dataList
[index
+ 1].toFloat() * danweiY
)} else {val centerX
= (grid_width1
* index
+ grid_width1
* (1 + index
)) / 2val centerY
=(dataList
[index
].toFloat() * danweiY
+ dataList
[index
+ 1].toFloat() * danweiY
) / 2val controX0
= (grid_width1
* index
+ centerX
) / 2val controY0
= (dataList
[index
].toFloat() * danweiY
+ centerY
) / 2val controX1
= (centerX
+ grid_width1
* (1 + index
)) / 2val controY1
= (centerY
+ dataList
[index
+ 1].toFloat() * danweiY
) / 2caves_path
.cubicTo(controX0
+ xMoveDistance
,controY0
+ yMoveDistance
,controX1
- xMoveDistance
,controY1
- yMoveDistance
,grid_width1
* (1 + index
),dataList
[index
+ 1].toFloat() * danweiY
)}}canvas
.nativeCanvas
.drawCircle(0f, 0f, 10f, text_paint
)canvas
.nativeCanvas
.drawPath(caves_path
, text_paint
)val line_paint
= android
.graphics
.Paint()line_paint
.strokeWidth
= 3fline_paint
.style
= android
.graphics
.Paint
.Style
.STROKEline_paint
.color
= android
.graphics
.Color
.argb(255, 212, 100, 77)canvas
.nativeCanvas
.drawPath(caves_path
, line_paint
)line_paint
.style
= android
.graphics
.Paint
.Style
.FILL
for (index
in 0 until dataList
.size
) {canvas
.nativeCanvas
.drawCircle(grid_width1
* index
,danweiY
* dataList
[index
],8f,line_paint
)}
}
到這里我們最大的困難也簡單的解決了吧?無非簡單的加減乘除是不是?接下來我們進行裝飾美麗的曲線,學過之前的文章對于這幾點技能我想我們都熟能生巧的創作了吧?漸變填充、動畫、點擊、`
6、曲線美化
- ??????????在自定義繪制中如果你對Compose的Api使用蹩腳,那完全可以通過 canvas.nativeCanvas將Canvas轉換為原生這樣你可以任意切換在兩個canvas之間操作????????????????
private fun DrawScope
.drawResultBitifull(canvas
: androidx
.compose
.ui
.graphics
.Canvas
) {val text_paint
= android
.graphics
.Paint()text_paint
.strokeWidth
= 2ftext_paint
.style
= android
.graphics
.Paint
.Style
.FILLtext_paint
.color
= android
.graphics
.Color
.argb(255, 0, 0, 0)text_paint
.textSize
= 66fval rectText
= Rect()val rectTextYuan
= Rect()canvas
.save()canvas
.scale(1f, -1f)canvas
.translate((size
.width
/ 2).toFloat() - 100, -500f)val text
= "1347"val textyu
= "元"text_paint
.getTextBounds(text
, 0, text
.length
, rectText
)canvas
.nativeCanvas
.drawText(text
,-rectText
.width().toFloat() - 42f,rectText
.height().toFloat() / 2,text_paint
)text_paint
.color
= android
.graphics
.Color
.argb(111, 111, 111, 111)text_paint
.getTextBounds(textyu
, 0, textyu
.length
, rectTextYuan
)text_paint
.textSize
= 33fcanvas
.nativeCanvas
.drawText(textyu
,80 + -rectTextYuan
.width().toFloat() - 42f,rectTextYuan
.height().toFloat() / 2,text_paint
)canvas
.translate(0f, 50f)canvas
.nativeCanvas
.drawText("較前天",-rectTextYuan
.width().toFloat() - 180f,rectTextYuan
.height().toFloat() / 2,text_paint
)canvas
.translate(100f, 0f)text_paint
.color
= android
.graphics
.Color
.argb(255, 223, 129, 120)canvas
.nativeCanvas
.drawText("+971.99(251.19%)",-rectTextYuan
.width().toFloat() - 180f,rectTextYuan
.height().toFloat() / 2,text_paint
)canvas
.translate(-100f, 50f)text_paint
.color
= android
.graphics
.Color
.argb(111, 111, 111, 111)canvas
.nativeCanvas
.drawText("對應圖中虛線部分進行最高評獎",-rectTextYuan
.width().toFloat() - 180f,rectTextYuan
.height().toFloat() / 2,text_paint
)canvas
.restore()
}
private fun drawHeaderToCanvas(canvas
: Canvas
,width
:Float
,marginToLeft
:Float
,dataList
:List
<Int
>,imgList
:ArrayList
<ImageBitmap
>) {val bitmap_paint
= android
.graphics
.Paint()bitmap_paint
.strokeWidth
= 2fbitmap_paint
.style
= android
.graphics
.Paint
.Style
.STROKEbitmap_paint
.xfermode
= PorterDuffXfermode(PorterDuff
.Mode
.SRC_IN
)bitmap_paint
.isAntiAlias
=truecanvas
.save()val srcRect1
=Rect(0, 0, 80, 80)val dstRect1
=Rect(0, 0, 40, 40)val x_scaleWidth
= (width
- marginToLeft
- 80f)val grid_width
= x_scaleWidth
/ 6val danweiY
= (grid_width
- 40) / 500for (index
in 0 until dataList
.size
) {val bitmap
= imgList
[index
].asAndroidBitmap()canvas
.save()canvas
.translate(grid_width
* index
- bitmap
.width
/20,danweiY
* dataList
[index
] + 20)val circlePath
= Path()circlePath
.addCircle(20f,20f, 20f, Path
.Direction
.CCW
)canvas
.clipPath(circlePath
)canvas
.drawBitmap(bitmap
, srcRect1
, dstRect1
, bitmap_paint
)canvas
.restore()}canvas
.restore()}@SuppressLint("ObsoleteSdkInt")
fun drawTextButton(canvas
: Canvas
) {val line_paint
= android
.graphics
.Paint()line_paint
.strokeWidth
= 2fline_paint
.style
= android
.graphics
.Paint
.Style
.STROKEline_paint
.color
= android
.graphics
.Color
.argb(188, 76, 126, 245)line_paint
.textSize
=32fval buttonPath
= android
.graphics
.Path()if (android
.os
.Build
.VERSION
.SDK_INT
>= android
.os
.Build
.VERSION_CODES
.LOLLIPOP
) {buttonPath
.addRoundRect(110f, -120f, 270f, -180f, 80f, 80f, android
.graphics
.Path
.Direction
.CCW
)}canvas
.drawPath(buttonPath
, line_paint
)canvas
.save()canvas
.scale(1f, -1f)line_paint
.style
= android
.graphics
.Paint
.Style
.FILLcanvas
.drawText("前 七 天", 140f, 165f, line_paint
)canvas
.restore()canvas
.save()canvas
.translate(260f, 0f)line_paint
.style
= android
.graphics
.Paint
.Style
.STROKEcanvas
.drawPath(buttonPath
, line_paint
)canvas
.scale(1f, -1f)line_paint
.style
= android
.graphics
.Paint
.Style
.FILLcanvas
.drawText("后 七 天", 140f, 165f, line_paint
)canvas
.restore()}
三、總結
- 自定義不僅提供了更方便的API,而且可以完全使用原來的API,所以原生Android開發還是很香的,Compose里面自定義完全沒想到再次分裝的點,但是對于我們來說不就多了一些選擇么。所以自定義完全沒問題。而且創造性完全不輸任何端,只是頂層的API定義有所不同罷了。
總結
以上是生活随笔為你收集整理的Jetpack-Compose-自定义绘制的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。