卯年ですね Composeでうさぎを描いてみよう

「うさぎうさぎ」創作投稿コンテストに参加しています 詳細は「うさぎうさぎ」創作投稿コンテスト をご覧ください今年は卯年になりました Composeで絵を描く開発者が多く見られた記憶があります昨年は 寅さん、寅さんいろいろですね 今年は卯年ということで ふと Compose でうさぎを描いてみたくなりました ところで、久しぶりに Compose を触ってみました.

準備

うさぎは主にキャンバスに描かれているので、まずキャンバスを生成し、次にキャンバスの幅、高さ、ブラシの色を決定する必要があります。

val drawColor = colorResource(id = R.color.color_EC4126)
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {

}
复制代码

幅と高さはハードコードされた 2 つの値です. システム API を使用して実際の画面の幅と高さを取得することもできます. ブラシの色は赤みを帯びています.

頭は実際には楕円です. canvas の drawPath メソッドを使用して描画します. 必要なのは、楕円の中心点の座標と、楕円の左上と右下の座標を決定することです.

val startX = screenWidth() / 4
val startY = screenHeight() / 3
val headPath = Path()
headPath.moveTo(screenWidth() / 2, screenHeight() / 2)
headPath.addOval(Rect(startX, startY, screenWidth() - startX, screenHeight() - startY))
headPath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = headPath, color = drawColor, style = Stroke(width = 12f))
}

复制代码

頭の中心点の x 軸と y 軸の座標はキャンバスの幅と高さの半分、左上の x 座標はキャンバス幅の 4 分の 1、y 座標はキャンバスの幅の 1/3 です。キャンバスの高さ、右下の x 座標がキャンバスの幅 左上の x 座標を引き、右下の y 座標は左上の y 座標を引き、最終的にこのパスを描画します。キャンバスの楕円、レンダリングを見てみましょう

tt1.png

頭を描いたら、耳を描きましょう. 2 つの耳は、実際には 2 つの楕円であり、中心線に対して対称です. 描画の考え方は、頭を描くのと同じです. 2 つのパスの中心点の座標を決定します. 、およびそれぞれの左上と右下の xy 座標

val leftEarPath = Path()
val leftEarPathX = screenWidth() * 3 / 8
val leftEarPathY = screenHeight() / 6
leftEarPath.moveTo(leftEarPathX, leftEarPathY)
leftEarPath.addOval(
    Rect(
        leftEarPathX - 60f,
        leftEarPathY / 2,
        leftEarPathX + 60f,
        startY + 30f
    )
)
leftEarPath.close()

val rightEarPath = Path()
val rightEarPathX = screenWidth() * 5 / 8
val rightEarPathY = screenHeight() / 6
rightEarPath.moveTo(rightEarPathX, rightEarPathY)
rightEarPath.addOval(
    Rect(
        rightEarPathX - 60f,
        rightEarPathY / 2,
        rightEarPathX + 60f,
        startY + 30f
    )
)
rightEarPath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = leftEarPath, color = drawColor, style = Stroke(width = 10f))
    drawPath(path = rightEarPath, color = drawColor, style = Stroke(width = 10f))
}
复制代码

レンダリングを見てください

tt2.png

内耳

このように耳はあまり立体的ではなく、少し平らに見えます. うさぎの耳は少し沈んだ感じになるので、このペアの耳に内耳を追加して立体感を高めます.内耳は実際には非常に単純です. 理由は外耳と同じです. 中心点と左上の点と右下の点のxy座標は小さくなります. 外耳の経路を変更できます.若干。

val leftEarSubPath = Path()
val leftEarSubPathX = screenWidth() * 3 / 8
val leftEarSubPathY = screenHeight() / 4
leftEarSubPath.moveTo(leftEarSubPathX, leftEarSubPathY)
leftEarSubPath.addOval(
    Rect(
        leftEarSubPathX - 30f,
        screenHeight() / 6,
        leftEarSubPathX + 30f,
        startY + 30f
    )
)
leftEarSubPath.close()

val rightEarSubPath = Path()
val rightEarSubPathX = screenWidth() * 5 / 8
val rightEarSubPathY = screenHeight() / 4
rightEarSubPath.moveTo(rightEarSubPathX, rightEarSubPathY)
rightEarSubPath.addOval(
    Rect(
        rightEarSubPathX - 30f,
        screenHeight() / 6,
        rightEarSubPathX + 30f,
        startY + 30f
    )
)
rightEarSubPath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = leftEarSubPath, color = drawColor, style = Stroke(width = 6f))
    drawPath(path = rightEarSubPath, color = drawColor, style = Stroke(width = 6f))
}
复制代码

レンダリングを見てください

tt31.png

インナーテイストがあり、内耳のブラシの太さを少し減らして、近くの大きな遠くの小さなハハハを強調するために、次のステップに行きましょう

耳を描いた後, 目を描き始めます. 目もとても描きやすいです. 主なことは中心点の位置を見つけることです. 中心点のx座標は実際にはのx座標と同じです.耳、および y 座標は頭の中心点の y 座標にわずかに近づきます。

val leftEyePath = Path()
val leftEyePathX = screenWidth() * 3 / 8
val leftEyePathY = screenHeight() * 11 / 24
leftEyePath.moveTo(leftEyePathX, leftEyePathY)
leftEyePath.addOval(
    Rect(
        leftEyePathX - 35f,
        leftEyePathY - 35f,
        leftEyePathX + 35f,
        leftEyePathY + 35f
    )
)
leftEyePath.close()

val rightEyePath = Path()
val rightEyePathX = screenWidth() * 5 / 8
val rightEyePathY = screenHeight() * 11 / 24
rightEyePath.moveTo(rightEyePathX, rightEyePathY)
rightEyePath.addOval(
    Rect(
        rightEyePathX - 35f,
        rightEyePathY - 35f,
        rightEyePathX + 35f,
        rightEyePathY + 35f
    )
)
rightEyePath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = leftEyePath, color = drawColor, style = Stroke(width = 10f))
    drawPath(path = rightEyePath, color = drawColor, style = Stroke(width = 10f))
}

复制代码

レンダリングは次のとおりです。

tt4.png

眼神有点空洞,无神是不,缺个眼珠子,那我们再给小兔子画上眼珠吧,眼珠就在眼睛的中心点位置,画一个圆点,圆点就要用到drawCircle,它有这些属性

fun drawCircle(
    color: Color,
    radius: Float = size.minDimension / 2.0f,
    center: Offset = this.center,
    /*@FloatRange(from = 0.0, to = 1.0)*/
    alpha: Float = 1.0f,
    style: DrawStyle = Fill,
    colorFilter: ColorFilter? = null,
    blendMode: BlendMode = DefaultBlendMode
)
复制代码

我们不需要用到全部,只需要用到颜色color,也就是红色,圆点半径radius,肯定要比眼睛的半径要小一点,我们就设置为10f,圆点中心坐标center,就是眼睛的中心点坐标,知道了以后我们开始绘制眼珠

Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) { 
    drawCircle(color = drawColor, radius = 10f, center = Offset(leftEyePathX,leftEyePathY))
    drawCircle(color = drawColor, radius = 10f, center = Offset(rightEyePathX,rightEyePathY))
}
复制代码

我们再看下效果图

画像.png

鼻子

接下去我们画鼻子,鼻子肯定在脑袋的中间,所以中心点x坐标就是脑袋中心点的x坐标,那鼻子的y坐标就设置成比中心点y坐标稍微高一点的位置,代码如下

val nosePath = Path()
val nosePathX = screenWidth() / 2
val nosePathY = screenHeight() * 13 / 24
nosePath.moveTo(nosePathX, nosePathY)
nosePath.addOval(Rect(nosePathX - 15f, nosePathY - 15f, nosePathX + 15f, nosePathY + 15f))
nosePath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) { 
    drawPath(path = nosePath, color = drawColor, style = Stroke(width = 10f))
}

复制代码

我们看下效果图

画像.png

兔唇

兔子的样子逐渐出来了,画完鼻子我们接着画啥呢?没错,兔子最有特点的位置也就是兔唇,我们脑补下兔唇长啥样子,首先位置肯定是在鼻子的下面,然后从鼻子开始往两边分叉,也就是两个扇形,扇形怎么画呢,我们也有现成的api,drawArc,我们看下drawArc都提供了哪些属性

fun drawArc(
    color: Color,
    startAngle: Float,
    sweepAngle: Float,
    useCenter: Boolean,
    topLeft: Offset = Offset.Zero,
    size: Size = this.size.offsetSize(topLeft),
    /*@FloatRange(from = 0.0, to = 1.0)*/
    alpha: Float = 1.0f,
    style: DrawStyle = Fill,
    colorFilter: ColorFilter? = null,
    blendMode: BlendMode = DefaultBlendMode
)
复制代码

我们需要用到的就是颜色color,这个扇形起始角度startAngle,扇形终止的角度sweepAngle,是否扇形两端跟中心点连接起来的布尔值useCenter,扇形的左上位置topLeft以及扇形的大小size也就是设置半径,知道这些以后我们开始逐个代入参数吧

Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) { 
    drawArc(
        color = drawColor,
        0f,
        120f,
        style = Stroke(width = 10f),
        useCenter = false,
        size = Size(120f, 120f),
        topLeft = Offset(nosePathX - 120f, nosePathY)
    )

    drawArc(
        color = drawColor,
        180f,
        -120f,
        style = Stroke(width = 10f),
        useCenter = false,
        size = Size(120f, 120f),
        topLeft = Offset(nosePathX + 10f, nosePathY)
    )
}
复制代码

画兔唇的时候其实就是在鼻子的两端各画一个坐标轴,左边的兔唇起始角度就是从x轴开始也就是0度,顺时针旋转120度,左上位置的x坐标刚好离开鼻子一个半径的位置,右边的兔唇刚好相反,逆时针旋转120度,起始角度是180度,左上位置的x坐标刚好在鼻子的位置那里,稍微加个10f让兔唇可以对称一些,我们看下效果图

画像.png

胡须

脸上好像空了点,兔子的胡须还没有呢,胡须其实就是两边各画三条线,用drawLine这个api,起始位置的x坐标跟眼睛中心点的x坐标一样,中间胡须起始位置的y坐标跟鼻子的y坐标一样,上下胡须的y坐标各减去一定的数值

Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) { 
    drawLine(
        color = drawColor,
        start = Offset(leftEyePathX, nosePathY - 60f),
        end = Offset(leftEyePathX - 250f, nosePathY - 90f),
        strokeWidth = 5f,
        cap = StrokeCap.Round
    )
    drawLine(
        color = drawColor,
        start = Offset(leftEyePathX, nosePathY),
        end = Offset(leftEyePathX - 250f, nosePathY),
        strokeWidth = 5f,
        cap = StrokeCap.Round
    )
    drawLine(
        color = drawColor,
        start = Offset(leftEyePathX, nosePathY + 60f),
        end = Offset(leftEyePathX - 250f, nosePathY + 90f),
        strokeWidth = 5f,
        cap = StrokeCap.Round
    )

    drawLine(
        color = drawColor,
        start = Offset(rightEyePathX, nosePathY - 60f),
        end = Offset(rightEyePathX + 250f, nosePathY - 90f),
        strokeWidth = 5f,
        cap = StrokeCap.Round
    )
    drawLine(
        color = drawColor,
        start = Offset(rightEyePathX, nosePathY),
        end = Offset(rightEyePathX + 250f, nosePathY),
        strokeWidth = 5f,
        cap = StrokeCap.Round
    )
    drawLine(
        color = drawColor,
        start = Offset(rightEyePathX, nosePathY + 60f),
        end = Offset(rightEyePathX + 250f, nosePathY + 90f),
        strokeWidth = 5f,
        cap = StrokeCap.Round
    )
}
复制代码

很简单的画了六条线,线的粗细也稍微设置的小一点,毕竟胡须还是比较细的,我们看下效果图

画像.png

就这样兔子脑袋部分所有元素都画完了,我们接着给兔子画身体

体は実際には楕円形で、その位置はキャンバスの下部のちょうど 3 分の 1 であり、左上の x 座標は頭の左上の x 座標よりも少し大きく、y-座標はキャンバスの 3 分の 2 で、右下の x 座標は頭の x 座標よりも下にあります座標は少し小さく、y 座標はキャンバスの下部です。頭のように体を描きます

val bodyPath = Path()
val bodyPathX = screenWidth() / 2
val bodyPathY = screenHeight() * 5 / 6
bodyPath.moveTo(bodyPathX, bodyPathY)
bodyPath.addOval(
    Rect(
        startX + 50f,
        screenHeight() * 2 / 3,
        screenWidth() - startX - 50f,
        screenHeight()
    )
)
bodyPath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = bodyPath, color = drawColor, style = Stroke(width = 10f))
}
复制代码

レンダリングは次のとおりです。

画像.png

2 つの爪

体を描いたらウサギの爪を描いてみましょう. 爪は実際には2つの楕円です. 楕円の中心のx座標は2つの目のx座標と同じで, y座標は5-です.キャンバスの 6 分の 1。

val leftHandPath = Path()
val leftHandPathX = screenWidth() * 3 / 8
val leftHandPathY = screenHeight() * 5 / 6
leftHandPath.moveTo(leftHandPathX, leftHandPathY)
leftHandPath.addOval(
    Rect(
        leftHandPathX - 35f,
        leftHandPathY - 90f,
        leftHandPathX + 35f,
        leftHandPathY + 90f
    )
)
leftHandPath.close()

val rightHandPath = Path()
val rightHandPathX = screenWidth() * 5 / 8
val rightHandPathY = screenHeight() * 5 / 6
rightHandPath.moveTo(rightHandPathX, rightHandPathY)
rightHandPath.addOval(
    Rect(
        rightHandPathX - 35f,
        rightHandPathY - 90f,
        rightHandPathX + 35f,
        rightHandPathY + 90f
    )
)
rightHandPath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = leftHandPath, color = drawColor, style = Stroke(width = 10f))
    drawPath(path = rightHandPath, color = drawColor, style = Stroke(width = 10f))
}
复制代码

レンダリングを見てみましょう

画像.png

しっぽ

まだ最後のステップです. ウサギの尻尾を描きましょう. 尻尾の中心点の x 座標は, キャンバスの幅から頭の右側の x 座標を引いたものです. y 座標尻尾の中心点の高さは、キャンバスの高さから特定の値を引いたものです. コードを見てみましょう.

val tailPath = Path()
val tailPathX = screenWidth() - startX
val tailPathY = screenHeight() - 200f
tailPath.moveTo(tailPathX, tailPathY)
tailPath.addOval(Rect(tailPathX - 60f, tailPathY - 90f, tailPathX + 60f, tailPathY + 90f))
tailPath.close()
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawPath(path = tailPath, color = drawColor, style = Stroke(width = 10f))
}
复制代码

こんな感じでうさぎを描き終えたので、完成形を見てみましょう

画像.png

そのように見えます.少し装飾しましょう.背景が少し単調であることがわかりました.結局のところ、それは中国の旧正月です.多くの場所で花火は許可されていませんが、それでも見ることができます.花火の写真を見つけるうさぎさんにあげて背景に使ってみましょう.drawImageみたいなキャンバスに絵が描けるAPIもあります.コードは以下の通りです.

val bgBitmap = ImageBitmap.imageResource(id = R.drawable.firework_night)
Canvas(
    modifier = Modifier
        .size(screenWidth().dp, screenHeight().dp)
        .background(color = Color.White)
) {
    drawImage(image = bgBitmap,
        srcOffset = IntOffset(0,0),
        srcSize = IntSize(bgBitmap.width,bgBitmap.height),
        dstSize = IntSize(screenWidth().toInt()*5/4,screenHeight().toInt()*5/4),
        dstOffset = IntOffset(0,0)
    )
}
复制代码

それがどのように機能するか見てみましょう

画像.png

うーん~~おしまい~~ あんまり格好良くない(笑)ポイントは美学ではなく旧正月の絵で、あとはComposeでCanvas APIを使うことです。個人的には、kotlinが徐々に成熟していくにつれて、ComposeはAndroid以降の主流のUI開発モードになりそうだと感じています

最後に、新年明けましておめでとうございます 今年も「うさぎ」が飛躍する一年になりますように~~

おすすめ

転載: juejin.im/post/7186454742950740028