C'est l'année du lapin, utilisons Compose pour dessiner des lapins

Je participe au concours de contribution créative "Lapin, un lapin". Pour plus de détails, veuillez consulter : Concours de contribution créative "Lapin, un lapin" . Cette année est venue l'année du lapin. Je me souviens avoir vu de nombreux développeurs dessiner avec Compose l'année dernière. Tigre, il y a toutes sortes de tigres. Maintenant que c'est l'année du lapin, j'ai soudainement envie d'utiliser Compose pour dessiner un lapin et l'essayer. Au fait, je n'ai pas touché à Compose depuis longtemps .

Préparation

Le lapin est principalement dessiné sur le canevas, nous devons donc d'abord générer un canevas, puis déterminer la largeur, la hauteur et la couleur du pinceau du canevas

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

}
复制代码

La largeur et la hauteur ne sont que deux valeurs codées en dur. Nous pouvons également utiliser l'API système pour obtenir la largeur et la hauteur réelles de l'écran. La couleur du pinceau est rougeâtre.

diriger

La tête est en fait une ellipse. Nous utilisons la méthode drawPath de canvas pour dessiner. Ce que nous devons faire est de déterminer les coordonnées du point central de l'ellipse, et les coordonnées en haut à gauche et en bas à droite de l'ellipse.

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

复制代码

Les coordonnées sur l'axe x et l'axe y du point central de la tête sont la moitié de la largeur et de la hauteur de la toile, la coordonnée x en haut à gauche est un quart de la largeur de la toile, la coordonnée y est un tiers de la hauteur de la toile, et la coordonnée x en bas à droite est la largeur de la toile Soustrayez la coordonnée x du coin supérieur gauche, et la coordonnée y du coin inférieur droit est de soustraire la coordonnée y du coin supérieur gauche, et enfin tracez le chemin de ce ellipse dans le Canvas, voyons le rendu

tt1.png

oreille

Après avoir dessiné la tête, dessinons les oreilles. Les deux oreilles sont en fait deux ellipses, qui sont symétriques par rapport à la ligne médiane. L'idée de dessin est la même que celle du dessin de la tête. Déterminez les coordonnées des points centraux des deux chemins , et les coordonnées xy du coin supérieur gauche et du coin inférieur droit de chaque

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))
}
复制代码

Regardez les rendus

tt2.png

oreille interne

De cette façon, les oreilles ne sont pas très tridimensionnelles et elles ont l'air un peu plates. Après tout, les oreilles de lapin se sentiront un peu enfoncées, nous ajoutons donc une oreille interne à cette paire d'oreilles pour augmenter l'effet tridimensionnel. L'oreille interne est en fait très simple, et la raison est la même que pour l'oreille externe. Les coordonnées xy du point central, du point supérieur gauche et du point inférieur droit seront plus petites. Nous pouvons modifier la trajectoire de l'oreille externe. un petit peu.

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))
}
复制代码

Regardez les rendus

tt31.png

Il y a un goût intérieur, et l'épaisseur du pinceau de l'oreille interne est légèrement réduite.Afin de mettre en évidence le proche grand loin petit hahaha, passons à l'étape suivante

Œil

Après avoir dessiné les oreilles, nous commençons à dessiner les yeux. Les yeux sont également très faciles à dessiner. L'essentiel est de trouver la position du point central. La coordonnée x du point central est en fait la même que la coordonnée x de l'oreille, et la coordonnée y est légèrement plus proche de la coordonnée y du point central de la tête. position précédente

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

复制代码

Les rendus sont les suivants

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))
}
复制代码

我们再看下效果图

image.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))
}

复制代码

我们看下效果图

image.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让兔唇可以对称一些,我们看下效果图

image.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
    )
}
复制代码

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

image.png

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

Corps

Le corps est en fait une ellipse, sa position n'est qu'un tiers du bas de la toile, la coordonnée x du coin supérieur gauche est un peu plus grande que la coordonnée x du coin supérieur gauche de la tête, le y- coordonnée est les deux tiers de la toile et la coordonnée x en bas à droite est inférieure à la coordonnée x de la tête Les coordonnées sont légèrement plus petites et la coordonnée y est le bas de la toile. Après le savoir, nous dessinera le corps comme la tête

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))
}
复制代码

Les rendus sont les suivants

image.png

deux griffes

Après avoir dessiné le corps, dessinons les griffes du lapin. Les griffes sont en fait deux ellipses. La coordonnée x du centre de l'ellipse est la même que la coordonnée x des deux yeux, et la coordonnée y est à cinq- sixièmes de la toile.

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))
}
复制代码

Voyons le rendu

image.png

Queue

C'est encore la dernière étape. Dessinons une queue pour le lapin. La coordonnée x du point central de la queue est la largeur de la toile moins la coordonnée de l'axe x sur le côté droit de la tête. La coordonnée y du point central de la queue est la hauteur de la toile moins une certaine valeur. Regardons le code

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))
}
复制代码

Je viens de finir de dessiner un lapin comme celui-ci, regardons le rendu final

image.png

Ça ressemble à ça, embellissons-le un peu. Nous avons trouvé l'arrière-plan un peu monotone. Après tout, c'est le Nouvel An chinois. Bien que les feux d'artifice ne soient pas autorisés dans de nombreux endroits, nous pouvons quand même y jeter un coup d'œil. Trouver une photo de feux d'artifice sur Internet et donnez-le à Rabbit Utilisons-le comme arrière-plan. Il existe également une API comme drawImage qui peut dessiner des images sur la toile. Le code est le suivant

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)
    )
}
复制代码

Voyons voir comment ça fonctionne

image.png

Hmm ~~ Vous avez terminé ~~ Cela ne semble pas très beau hahaha, mais le point n'est pas pour l'esthétique, mais pour une photo du Nouvel An chinois, et l'autre est d'utiliser les API Canvas dans Compose. tout, à mesure que kotlin mûrit progressivement, je pense personnellement que Compose est susceptible de devenir le mode de développement d'interface utilisateur courant après Android

Enfin, je souhaite à tous une bonne année. Je souhaite à tous une bonne année du lapin, et le "lapin" fera de grands progrès ~~

Je suppose que tu aimes

Origine juejin.im/post/7186454742950740028
conseillé
Classement