Android custom View planet Movement

Welcome to my personal blog

I found an interesting animation of planet movement when I was wandering in Dribbble. It happened to be in good time recently, so I simply realized the middle part of movement. Due to the time, the initial part of displacement was not completed.

The design process

The old way is to break down the composition of the animation. The entire animation can be seen as a spinning planet moving from small to large in the upper right corner to the center of the screen.

The displacement and scaling of the planet are not mentioned (in fact, there is a demand recently, so there is no time to improve it), but the rotation and tail processing of the planet are mainly improved.

At the bottom are the stars flashing in the background, appearing randomly and scaling each time within a certain range of the planet

When we first designed the tail effect, we designed two end lines in no column. It’s constantly moving and moving. But the implementation is messy. Finally, the method of first drawing all the contents displayed in the tail, and then covering and moving this part with the same color as the background to form a visual effect is adopted (PorterDuff mode can also be set to display). The results in the design process are as follows

The design of the star, the star itself using simple cover and Bezier curve can complete a more satisfactory background of the star.

The focus is on the design of the planet’s surface and the movement of surface patterns as the planet rotates. The solution is to draw three repeated and continuous surface patterns and simulate the rotation of the planet by moving the entire surface pattern. Finally, PorterDuff is used to control the position of the displayed parts and the stars.

When PorterDuff mode is not enabled, draw the following style:

After the PorterDuff mode is enabled, the graph of the specified shape is displayed in the specified position as follows:

Finally, you can simulate the rotation of the planet by moving the landscape

Code implementation

Stars in the background

private fun drawStarts(canvas: Canvas, perIndexInAll: Float) {
    // Stars in the background appear randomly in the vicinity of the star
    val maxRand = 800

    canvas.translate(-maxRand / 2F , -maxRand / 2F)
    val Random = Random(perIndexInAll.toInt().toLong())

    // Draw the background stars
    for (index in 0.4){
        drawStart(canvas ,  Random.nextFloat() * maxRand , Random.nextFloat() * maxRand , perIndex)
    }

    canvas.translate(maxRand / 2F , maxRand / 2F)}// Draw the background star content
// Draw the background star content
private fun drawStart(canvas: Canvas, x: Float, y: Float, per: Float) {
    var per = per
    // This part is for the stars to change from small to large and then from large to small
    if (per >= 1.0 F){
        per -= 1F
    }
    if (per <= 0.5 F){
        per *= 2
    }else{
        per = (1 - per) * 2
    }

    canvas.save()
    canvas.translate(x , y)

    canvas.scale(per , per)

    val paint = Paint()
    paint.color = 0xff78D8DF.toInt()

    val startLength = 30F
    val startOffset = startLength / 3F

    // Trace the shapes of the stars through the paths
    val path = Path()
    path.moveTo(0F , startLength)
    path.lineTo(startOffset , startOffset )
    path.lineTo(startLength , 0F)
    path.lineTo(startOffset  , -startOffset )
    path.lineTo(0F , -startLength)
    path.lineTo(-startOffset  , -startOffset )
    path.lineTo(-startLength , 0F)
    path.lineTo(-startOffset  , startOffset )
    path.lineTo(0F , startLength)

    canvas.drawPath(path , paint)

    paint.color = viewBackgroundColor
    // Draw the inner shape of the star by zooming out
    canvas.scale(0.3 F , 0.3 F)
    canvas.drawPath(path , paint)

    canvas.restore()
}
Copy the code

Planet outside

private fun drawGas(canvas: Canvas, index: Float) {
    canvas.save()
    canvas.rotate(45F)

    val gasWidth = 18F
    val baseR = baseR * 0.7 F
    val absBaseR = baseR / 5F

    val paint = Paint()
    paint.strokeWidth = gasWidth
    paint.style = Paint.Style.STROKE
    paint.color = 0xff2F3768.toInt()

    val paintArc = Paint()
    paintArc.color = 0xff2F3768.toInt()

    val gasLength = baseR * 2F
    canvas.save()

    val gsaL = gasWidth / 2F * 3
    var maxGasLength = (gasLength + gsaL ) / 2
    var index = index

    canvas.scale(1F , -1F)

    // Draw the air flow behind the star
    // There are so many defined variables
    // I don't want to write a function with a lot of arguments
    canvas.save()
    canvas.translate(baseR , baseR * 1.2 F)
    canvas.translate(0F , absBaseR)
    // The drawLines function draws a line segment with semicircles at both ends
    drawLines(0F, maxGasLength, canvas, paint)
    drawWhite( maxGasLength * index, gasWidth , gsaL * 2 , canvas)
    drawWhite( maxGasLength * (index - 1 ) * 1.1 F, gasWidth , gsaL * 2 , canvas)
    drawWhite( maxGasLength * (index + 1 ) * 1.1 F, gasWidth , gsaL * 2 , canvas)
    canvas.restore()

    index = index + 0.3 F

    / /... There is no duplication of code without writing functions

    val rectf = RectF(-baseR , -baseR , baseR ,baseR)
    canvas.drawArc(rectf , 0F , 180F , false , paint)

    canvas.drawLine(baseR ,0F ,  baseR ,  -baseR, paint)
    canvas.drawLine(-baseR ,0F ,  -baseR ,  -baseR, paint)

    canvas.restore()
}

// Draw the trailing whitespace
private fun drawWhite(offset: Float, gasWidth: Float, gsaL : Float , canvas: Canvas) {
    val r = gasWidth / 2F

    canvas.save()
    canvas.translate( 0F , offset - 2 * gsaL )

    val pointPaint = Paint()
    pointPaint.strokeWidth = 20F
    pointPaint.color = Color.RED

    // Draw a semicircle effect using bezier curves
    val path = Path()
    path.moveTo(-r , gsaL)
    path.cubicTo(
            - r * C ,  gsaL - r,
            r * C ,  gsaL - r,
            r , gsaL
    )

    path.lineTo(r , - gsaL)
    path.cubicTo(
            r * C ,  - gsaL + r,
            -r * C ,  - gsaL + r,
            -r , - gsaL
    )

    path.lineTo(-r , gsaL * 1.5 F)

    val paint = Paint()
    paint.color = viewBackgroundColor
    canvas.drawPath(path , paint)

    canvas.restore()
}
Copy the code

planet

private fun drawPlanet(canvas: Canvas , index : Float) {
    // Set the original layer
    val srcB = makeSrc(index)
    // Set the mask layer
    // The mask layer has only one circle the size of the planet
    val dstB = makeDst(index)

    val paint = Paint()
    canvas.saveLayer(-baseR, -baseR, baseR , baseR, null, Canvas.ALL_SAVE_FLAG)
    // Draw the mask layer
    canvas.drawBitmap(dstB,  -baseR / 2F, -baseR / 2F , paint)
    // Set mask mode to SRC_IN to display the part of the original layer that intersects the mask layer
    paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
    canvas.drawBitmap(srcB, width / -2F, height / -2F , paint)
    paint.xfermode = null
}


// Set the source layer
fun makeSrc(index :Float): Bitmap {
    val bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bm)
    canvas.translate(width.toFloat() / 2F , height.toFloat() / 2F)

    val paint = Paint()
    paint.color = 0xff57BEC6.toInt()
    paint.style = Paint.Style.FILL

    val rectf = RectF(-baseR / 2F, -baseR / 2F, baseR / 2F, baseR / 2F)
    canvas.drawArc(rectf , 0F , 360F , true , paint)

    canvas.save()


    // Draw the star background
    paint.color = 0xff78D7DE.toInt()
    var baseR = baseR * 0.9.toFloat()
    val rectf2 = RectF(-baseR / 2F, -baseR / 2F, baseR / 2F, baseR / 2F)
    canvas.translate(baseR / 6F , baseR / 6F)
    canvas.drawArc(rectf2 , 0F , 360F , true , paint)

    canvas.restore()
    canvas.rotate(-45F)
    canvas.save()

    val bottomBaseR = baseR / 0.9 F / 2
    val path = Path()
    path.moveTo(-bottomBaseR , 0F)
    path.cubicTo(-bottomBaseR , bottomBaseR * 2, bottomBaseR  , bottomBaseR * 2, bottomBaseR , 0F)

    path.cubicTo(
            bottomBaseR * C,bottomBaseR ,
            -bottomBaseR * C,bottomBaseR ,
            -bottomBaseR , 0F
    )

    // Draw a shadow effect on the star background
    paint.color = 0xffAAEEF2.toInt()
    paint.style = Paint.Style.FILL
    canvas.drawPath(path , paint)

    // Map the landscape of the planet
    drawPoints(index , canvas)

    canvas.restore()

    paint.strokeWidth = 30F
    paint.color = 0xff2F3768.toInt()
    paint.style = Paint.Style.STROKE
    canvas.drawArc(rectf , 0F , 360F , true , paint)

    return bm
}

private fun drawPoints(index: Float, canvas: Canvas) {
        val paintB = Paint()
        val paintS = Paint()
        paintS.style = Paint.Style.FILL
        paintS.color = 0xffE7F2FB.toInt()

        paintB.style = Paint.Style.FILL
        paintB.color = 0xff2F3768.toInt()

        val baseRB = baseR / 2F / 3
        val baseRS = baseR / 2F / 3 / 3

        val rectfB = RectF(-baseRB, -baseRB, baseRB, baseRB)
        val rectfS = RectF(-baseRS, -baseRS, baseRS, baseRS)

        val pointPaint = Paint()
        pointPaint.color = Color.BLACK
        pointPaint.strokeWidth = 50F

        val coverWidth = baseR

        // Simulate the rotation effect of the star by moving the origin of coordinates
        canvas.translate(-coverWidth / 2F , coverWidth * 1.5 F)

        val index = index
        canvas.translate(0F , coverWidth * index )

        // Repeat the map three times to make the rotation seamless
        for (i in 0.2){
            canvas.save()
            canvas.translate(coverWidth / 3F / 2  , -coverWidth / 3F * 2)
            canvas.drawArc(rectfB , 0F , 360F , true , paintB)
            canvas.drawArc(rectfS , 0F , 360F , true , paintS)
            canvas.restore()

            canvas.save()
            canvas.translate(coverWidth / 3F *2 , -coverWidth / 3F)
            canvas.drawArc(rectfB , 0F , 360F , true , paintB)
            canvas.drawArc(rectfS , 0F , 360F , true , paintS)
            canvas.restore()

            canvas.save()
            canvas.translate(coverWidth / 3F *2 , -coverWidth / 8F * 7 + -coverWidth / 10F )
            canvas.drawArc(rectfS , 0F , 360F , true , paintB)
            canvas.restore()

            canvas.save()
            canvas.translate(coverWidth / 3F *2 , -coverWidth / 8F * 7  - -coverWidth / 10F )
            canvas.drawArc(rectfS , 0F , 360F , true , paintB)
            canvas.restore()

            canvas.translate(0F , -coverWidth)
        }
    }
Copy the code

You can visit my GitHub to get the relevant code, welcome everyone to start or provide suggestions.