Es ist das Jahr des Kaninchens, verwenden wir Compose, um Kaninchen zu zeichnen

Ich nehme am Creative Contribution Contest „Rabbit, a Rabbit" teil. Weitere Informationen finden Sie unter: Creative Contribution Contest „Rabbit, a Rabbit" . Dieses Jahr ist das Jahr des Hasen angebrochen. Ich erinnere mich, dass viele Entwickler mit Compose gezeichnet haben letztes Jahr. Tiger, es gibt alle Arten von Tigern. Jetzt, wo das Jahr des Hasen ist, möchte ich plötzlich mit Compose einen Hasen zeichnen und ausprobieren. Übrigens habe ich Compose schon lange nicht mehr angerührt .

Vorbereitung

Der Hase wird hauptsächlich auf die Leinwand gezeichnet, also müssen wir zuerst eine Leinwand erzeugen und dann die Breite, Höhe und Pinselfarbe der Leinwand bestimmen

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

}
复制代码

Breite und Höhe sind nur zwei hartcodierte Werte. Wir können auch die System-API verwenden, um die tatsächliche Bildschirmbreite und -höhe zu erhalten. Die Pinselfarbe ist rötlich.

Kopf

Der Kopf ist eigentlich eine Ellipse. Wir verwenden die drawPath-Methode von canvas zum Zeichnen. Was wir tun müssen, ist, die Koordinaten des Mittelpunkts der Ellipse und die oberen linken und unteren rechten Koordinaten der Ellipse zu bestimmen.

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

复制代码

Die x-Achsen- und y-Achsenkoordinaten des Mittelpunkts des Kopfes sind die Hälfte der Breite und Höhe der Leinwand, die x-Koordinate oben links ist ein Viertel der Leinwandbreite, die y-Koordinate ist ein Drittel der Leinwandbreite Leinwandhöhe und die x-Koordinate unten rechts ist die Leinwandbreite. Subtrahieren Sie die x-Koordinate oben links und die y-Koordinate unten rechts, um die y-Koordinate oben links zu subtrahieren, und zeichnen Sie schließlich den Pfad davon Ellipse im Canvas-Bereich, sehen wir uns das Rendering an

tt1.png

Ohr

Nachdem wir den Kopf gezeichnet haben, zeichnen wir die Ohren. Die beiden Ohren sind eigentlich zwei Ellipsen, die symmetrisch zur Mittellinie sind. Die Zeichenidee ist die gleiche wie beim Zeichnen des Kopfes. Bestimmen Sie die Koordinaten der Mittelpunkte der beiden Pfade , und die xy-Koordinaten jeweils oben links und unten rechts

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

Schauen Sie sich die Renderings an

tt2.png

Innenohr

Auf diese Weise sind die Ohren nicht sehr dreidimensional und sehen etwas flach aus, da sich Hasenohren ein wenig eingesunken anfühlen, also fügen wir diesem Paar Ohren ein Innenohr hinzu, um den dreidimensionalen Effekt zu verstärken. Das Innenohr ist eigentlich sehr einfach, und der Grund ist derselbe wie beim Außenohr. Die xy-Koordinaten des Mittelpunkts und des oberen linken Punkts und des unteren rechten Punkts werden kleiner. Wir können den Pfad des äußeren Ohrs a ändern ein kleines bisschen.

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

Schauen Sie sich die Renderings an

tt31.png

Es gibt einen inneren Geschmack und die Dicke der Bürste des Innenohrs ist leicht reduziert.Um das fast große weit kleine Hahaha hervorzuheben, gehen wir zum nächsten Schritt

Auge

Nachdem wir die Ohren gezeichnet haben, beginnen wir mit dem Zeichnen der Augen. Die Augen sind auch sehr einfach zu zeichnen. Die Hauptsache ist, die Position des Mittelpunkts zu finden. Die x-Koordinate des Mittelpunkts ist eigentlich dieselbe wie die x-Koordinate von dem Ohr, und die y-Koordinate ist etwas näher an der y-Koordinate des Kopfmittelpunktes vorherige Position

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

复制代码

Die Renderings sind wie folgt

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

我们再看下效果图

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

复制代码

我们看下效果图

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

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

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

Bild.png

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

Körper

Der Körper ist eigentlich eine Ellipse, seine Position ist nur ein Drittel der Unterseite der Leinwand, die x-Koordinate oben links ist etwas größer als die x-Koordinate oben links des Kopfes, die y- Die Koordinate ist zwei Drittel der Leinwand, und die x-Koordinate unten rechts ist niedriger als die x-Koordinate des Kopfes. Die Koordinaten sind etwas kleiner und die y-Koordinate ist der untere Rand der Leinwand. Nachdem wir es wissen wird den Körper wie den Kopf zeichnen

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

Die Renderings sind wie folgt

Bild.png

zwei Krallen

Nachdem wir den Körper gezeichnet haben, zeichnen wir die Krallen des Kaninchens. Die Krallen sind eigentlich zwei Ellipsen. Die x-Koordinate des Zentrums der Ellipse ist die gleiche wie die x-Koordinate der beiden Augen, und die y-Koordinate liegt bei fünf- Sechstel der Leinwand.

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

Sehen wir uns die Wiedergabe an

Bild.png

Schwanz

Es ist immer noch der letzte Schritt. Lassen Sie uns einen Schwanz für den Hasen zeichnen. Die x-Koordinate des Mittelpunkts des Schwanzes ist die Breite der Leinwand minus der x-Achsenkoordinate auf der rechten Seite des Kopfes. Die y-Koordinate des Mittelpunkts des Schwanzes ist die Höhe der Leinwand minus einem bestimmten Wert. Sehen wir uns den Code an

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

Wir haben gerade ein solches Kaninchen gezeichnet, werfen wir einen Blick auf das endgültige Rendering

Bild.png

So sieht es aus, lasst es uns ein wenig verschönern. Wir fanden den Hintergrund etwas eintönig. Immerhin ist chinesisches Neujahr. Obwohl Feuerwerk vielerorts nicht erlaubt ist, können wir trotzdem einen Blick darauf werfen. Finden Sie ein Bild von Feuerwerk im Internet und geben Sie es an Rabbit. Verwenden wir es als Hintergrund. Es gibt auch eine API wie drawImage, die Bilder auf die Leinwand zeichnen kann. Der Code lautet wie folgt

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

Mal sehen, wie es funktioniert

Bild.png

Hmm~~ Sie sind fertig~~ Es sieht nicht sehr gut aus, hahaha, aber es geht nicht um die Ästhetik, sondern um ein chinesisches Neujahrsbild, und das andere ist die Verwendung der Canvas-APIs in Compose Während Kotlin allmählich reift, glaube ich persönlich, dass Compose nach Android wahrscheinlich zum Mainstream-UI-Entwicklungsmodus werden wird

Abschließend möchte ich allen ein frohes neues Jahr wünschen. Ich wünsche allen ein frohes Jahr des Hasen, und der "Hase" wird große Fortschritte machen~~

Ich denke du magst

Origin juejin.im/post/7186454742950740028
Empfohlen
Rangfolge