我正在参与「兔了个兔」构思投稿大赛,概况请看:「兔了个兔」构思投稿大赛,今年现已来到了兔年,记住去年的时候,看到过不少开发者用Compose画过山君,各式各样的山君都有,那已然现已是兔年了,忽然也想着用Compose来画只兔子试试看,趁便练练手,究竟也好久没碰Compose了

准备工作

兔子主要仍是画在画布上面,所以咱们首要得生成个Canvas,然后确定Canvas的宽高跟画笔色彩

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

宽高这边仅仅写死的两个数值,咱们也能够用体系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坐标便是画布宽的四分之一,y坐标便是画布高的三分之一,右下的x坐标便是画布宽减去左上的x坐标,右下的y坐标便是减去左上的y坐标,最终在Canvas里边将这个椭圆的path制作出来,咱们看下作用图

兔年了,一起用Compose来画兔子吧

耳朵

画完脑袋咱们接着画耳朵,两只耳朵其实也便是两个椭圆,别离以中心线左右对称,制作思路同画脑袋相同,确定两个path的中心点坐标,以及各自左上跟右下的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))
}

看下作用图

兔年了,一起用Compose来画兔子吧

内耳

这样感觉耳朵不是很立体,看起来有点平面,究竟兔耳朵会有点往里凹的感觉,所以咱们给这副耳朵加个内耳增加点立体感,内耳其实很简单,道理同外面的耳朵相同,仅仅中心点跟左上点,右下点的xy坐标会小一点,咱们略微改一下外耳的path就能够了

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

看下作用图

兔年了,一起用Compose来画兔子吧

有内味儿了,内耳的画笔粗细略微调小了一点,为了突出个近大远小嘛哈哈哈,咱们接着下一步

眼睛

画完耳朵咱们开端画眼睛了,眼睛也很好画,主要是先找到中心点方位就好,中心点的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))
}

作用图如下

兔年了,一起用Compose来画兔子吧

目光有点空洞,无神是不,缺个眼球子,那咱们再给小兔子画上眼球吧,眼球就在眼睛的中心点方位,画一个圆点,圆点就要用到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))
}

咱们再看下作用图

兔年了,一起用Compose来画兔子吧

鼻子

接下去咱们画鼻子,鼻子必定在脑袋的中心,所以中心点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))
}

咱们看下作用图

兔年了,一起用Compose来画兔子吧

兔唇

兔子的姿态逐渐出来了,画完鼻子咱们接着画啥呢?没错,兔子最有特色的方位也便是兔唇,咱们脑补下兔唇长啥姿态,首要方位必定是在鼻子的下面,然后从鼻子开端往两头分叉,也便是两个扇形,扇形怎么画呢,咱们也有现成的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让兔唇能够对称一些,咱们看下作用图

兔年了,一起用Compose来画兔子吧

胡须

脸上如同空了点,兔子的胡须还没有呢,胡须其实便是两头各画三条线,用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
    )
}

很简单的画了六条线,线的粗细也略微设置的小一点,究竟胡须仍是比较细的,咱们看下作用图

兔年了,一起用Compose来画兔子吧

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

身体

身体其实也是个椭圆,方位刚好在画布下方三分之一的方位,左上x坐标比脑袋左上x坐标大一点,y坐标便是画布三分之二的方位处,右下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))
}

作用图如下

兔年了,一起用Compose来画兔子吧

双爪

画完身体咱们再画兔子的双爪,双爪其实也是画两个椭圆,椭圆中心点的x坐标同两只眼睛的x坐标相同,y坐标在画布六分之五的方位

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

咱们看下作用图

兔年了,一起用Compose来画兔子吧

尾巴

还差最终一步,咱们给兔子画上尾巴,尾巴的中心点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))
}

就这样一只兔子画完了,咱们看下最终作用图

兔年了,一起用Compose来画兔子吧

看起来像那么回事了,咱们再略微点缀下,布景咱们发现还有点单调,究竟是春节了嘛,尽管多地不让放焰火,但咱们看看仍是能够的,网上找张焰火图片给兔子当布景吧,刚好也有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)
    )
}

咱们来看下作用怎么样

兔年了,一起用Compose来画兔子吧

嗯~~功德圆满~~如同也不是很好看哈哈哈,不过重点咱也不是为了美观,而是一个春节了图个涵义,另一个便是用下Compose里边Canvas这些api,究竟跟着kotlin逐渐成熟,个人感觉Compose很有或许成为Android今后干流的UI开发形式

最终给我们拜个早年了,祝我们兔年大吉,“兔”飞猛进~~