android custom control -- drawing an underwater world

Posted by Tsukiyomi on Sun, 30 Jan 2022 03:19:03 +0100

Welcome to my official account, "An Anan Android", to learn more knowledge.

Read a good book—— I said

In the previous articles, we talked about Canvas, Paint and Path commonly used in custom controls. In this article, we go further on the basis of the previous articles and draw the effect of an underwater world.

Project github address

git entry

Drawing process

Draw background

First of all, the sea should be blue, and it should be a gradient. Then, according to the content of paint, we can use paint Setshader is implemented by using canvas Draw the whole canvas with the drawRect method, and set a Shader with the paint brush. The code is as follows:

paintBack.shader = LinearGradient(
            measuredWidth / 3f,
            0f,
            measuredWidth * 2 / 3f,
            measuredHeight.toFloat(),
            Color.WHITE,
            Color.parseColor("#000055"),
            Shader.TileMode.CLAMP
        )
canvas.drawRect(background, paintBack)

The effect is like this

Drawing whales

Cough, I found a picture of whale on Baidu

Then the most beautiful big blue whale was pulled out with ps, and the tail of the blue whale was treated with ps liquefaction tool. The purpose is to make the tail of the blue whale swing constantly in the process of movement, so as to achieve the effect of swimming




code:
 private val bitmaps = listOf<Bitmap>(
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish1),
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish2),
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish3),
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish4),
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish3),
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish2),
        BitmapFactory.decodeResource(context.resources, R.drawable.bluefish1),
    )
 canvas.run {
            drawBitmap(bitmaps[bitmapIndex % bitmaps.size], 100f, 100f, paint)
            bitmapIndex++
            drawFishWithPath()
        }
Effect achieved:

It's not enough for a killer whale to swing its tail. We should also allow him to swim. It's best to follow the path we specify

The core code that defines the path
 fishPath.moveTo(100f, 100f)
        for (i in 0..20) {
            fishPath.apply {
                cubicTo(randowX(), randowY(), randowX(), randowY(), randowX(), randowY())
            }
        }
        fishPath.close()
Draw the path effect

There's a problem here, because there are many sharp angles when the path turns. It's conceivable that this won't work, and the baby killer whale won't swim like this, right? The important thing for subsequent optimization is to achieve the effect

Let the fish move along the path

Here we need to use the PathMeasure tool

dstPath.reset()
        var stop = start+100f
        pathMeasure.getSegment(start, stop, dstPath, true)
        val matrix =  Matrix()
        pathMeasure.getMatrix(stop, matrix, (PathMeasure.POSITION_MATRIX_FLAG.or(PathMeasure.TANGENT_MATRIX_FLAG)))
        val bitmap = bitmaps[bitmapIndex % bitmaps.size]
        matrix.preTranslate(-bitmap.width / 2f, -bitmap.height / 2f)
        canvas.drawBitmap(bitmap, matrix, paint)

The code is through pathmeasure The getsegment method can intercept the path and draw a whale picture at a certain position of the path. Because we can get the tangent value of a point of the path, our whales can always swim along the tangent

Swimming effect:

Unfortunately, I forgot to record the screen in the middle, so I had to put the final effect picture on it

Draw the sun and bubbles

Now that the final effect has come out, let's put the code to draw the sun and bubbles directly behind

Draw the sun

The main point of drawing the sun is to draw the surrounding sunshine. When drawing sunlight, we divide the solar radian into 20 equal parts, and then draw a triangle between the two equal parts to achieve our sunshine effect.

path.moveTo(radius + sunX, sunY)
        val degree = 3.14f * 2 / leafNum
        for (i in 1..leafNum) {
            val x1 = radius * cos(i * degree) + sunX
            val y1 = radius * sin(i * degree) + sunY

            val halfDegree = (i - 0.5) * degree
            val shineRadius = radius + Random.nextInt(50)
            val controllX = shineRadius * cos(halfDegree).toFloat() + sunX
            val controllY =  shineRadius * sin(halfDegree).toFloat() + sunY
            path.lineTo(controllX, controllY)
            path.lineTo(x1, y1)
        }
        path.close()
Draw bubbles

We use radiogradient to mirror the gradient to achieve the bubble effect. It should be noted that the middle point of the color of the mirror gradient should not be selected at the center of the circle, otherwise it will be ugly

paint.shader=RadialGradient(cycleX+40,cycleY-40,radius+300, Color.WHITE,Color.GREEN,Shader.TileMode.CLAMP)
        canvas.drawCircle(cycleX, cycleY, radius, paint)

Topics: Android