Cool 3D sphere text cloud effect!

Posted by indigo2k on Tue, 28 Dec 2021 09:59:36 +0100

cause

I saw a h5 dazzling 3D sphere text effect on the Internet a few days ago. I felt it was very interesting. I was ready to reproduce it on the Android side. Less nonsense, let's see the effect first (gif looks like some cards, but it won't)

Core principles

Text coordinates

The first thing to do is to determine a coordinate for each text. Android uses the left-hand coordinate system, and our effect is a sphere, so I use the spherical coordinate system to calculate the coordinates of each text.

y = radius * cos(Math.toRadians(this.upDegree))
z = -radius * sin(Math.toRadians(this.upDegree)) * sin(Math.toRadians(this.bottomDegree))
x = radius * sin(Math.toRadians(this.upDegree)) * cos(Math.toRadians(this.bottomDegree))

Where radius is the length of the connecting line from the center of the circle to the sphere, that is, the radius of the sphere, upDegree is the included angle between the connecting line and the positive direction of the y axis, with the range of [0180], and bottomDegree is the included angle between the projection of the connecting line on the plane determined by the xz axis and the positive direction of the x axis, with the range of [0360]

Text color and size

When the angle between the text and the positive direction of the x-axis is 90 degrees, the text is the largest and the color is the deepest. At 270 degrees, the text is the smallest and the color is the lightest. 270 degrees to 360 degrees is the reverse process of the above process. Therefore, we define a variable factor to describe the change degree of text color and size. The range is [minFactor, 1]. minFactor can be passed in through external variables.

According to the previous description, we can determine that the function of factor is

 factor = minFactor.coerceAtLeast(
            when (bottomDegree) {
                in 0.0..90.0 -> {
                    1.0 / Math.PI * Math.toRadians(bottomDegree) + 0.5
                }
                in 270.0..360.0 -> {
                    1.0 / Math.PI * Math.toRadians(bottomDegree) - 1.5
                }
                else -> {
                    -1.0 / Math.PI * Math.toRadians(bottomDegree) + 1.5
                }
            }
        )

By constructing three piecewise linear functions from different angles.

Calculate text coordinates

Define the WordItem class to represent each text, coordinates and its corresponding factor. When onMeasure, calculate the corresponding coordinates for all text and store them in the wordItemList member variable.

class WordItem(
    var text: String,
    var upDegree: Double = 0.0,
    var bottomDegree: Double = 0.0,
    var x: Double = 0.0,
    var y: Double = 0.0,
    var z: Double = 0.0,
    var factor: Double = 0.0
) {

    fun cal(radius: Double, upDegree: Double, bottomDegree: Double, minFactor: Double) {
        this.upDegree = upDegree % 180
        this.bottomDegree = bottomDegree % 360
        y = radius * cos(Math.toRadians(this.upDegree))
        z = -radius * sin(Math.toRadians(this.upDegree)) * sin(Math.toRadians(this.bottomDegree))
        x = radius * sin(Math.toRadians(this.upDegree)) * cos(Math.toRadians(this.bottomDegree))
        factor = minFactor.coerceAtLeast(
            when (bottomDegree) {
                in 0.0..90.0 -> {
                    1.0 / Math.PI * Math.toRadians(bottomDegree) + 0.5
                }
                in 270.0..360.0 -> {
                    1.0 / Math.PI * Math.toRadians(bottomDegree) - 1.5
                }
                else -> {
                    -1.0 / Math.PI * Math.toRadians(bottomDegree) + 1.5
                }
            }
        )
    }

    fun move(radius: Double, upOffset: Double, bottomOffset: Double, minFactor: Double) {
        cal(radius, upDegree + upOffset, bottomDegree + bottomOffset, minFactor)
    }
}
private fun genWordItemList(): MutableList<WordItem>? {
        wordList?.let { list ->
            val wordItemList = mutableListOf<WordItem>()
            var upDegree = 0.0
            for (row in 0 until circleRowNum) {
                upDegree += upDegreeGap
                upDegree %= 180.0
                var bottomDegree = 0.0
                for (col in 0 until perNumInCircle) {
                    val index = row * perNumInCircle + col
                    if (index < wordList?.size ?: 0) {
                        bottomDegree += bottomDegreeGap
                        bottomDegree %= 360.0
                        val wordItem = WordItem(list[index])
                        wordItem.cal(radius, upDegree, bottomDegree, minFactor)
                        wordItemList.add(wordItem)
                    }
                }
            }
            return wordItemList
        }
        return null
    }

Draw text

First, set the size of the brush text and the corresponding alpha value according to the factor, then calculate its corresponding position according to the text size, draw, and continuously increase the bottomDegreeOffset to modify the coordinates of each text to realize rotation.

canvas?.let { canvas ->
            wordItemList?.forEach { wordItem ->
                wordItem.move(radius, 0.0, 1.0, minFactor)
                paint.textSize = (wordItem.factor * maxTextSize).toFloat()
                paint.alpha = 30.coerceAtLeast((wordItem.factor * 255).toInt())
                textRect.setEmpty()
                paint.getTextBounds(wordItem.text, 0, wordItem.text.length, textRect)
                canvas.drawText(
                    wordItem.text,
                    ((width - paddingLeft - paddingRight) / 2 + wordItem.x - textRect.width() / 2).toFloat(),
                    ((height - paddingTop - paddingBottom) / 2 + wordItem.y - textRect.height() / 2).toFloat(),
                    paint
                )
            }
            postInvalidate()
        }

Advanced notes of Android advanced development system, latest interview review notes PDF, My GitHub

end of document

Your favorite collection is my greatest encouragement!
Welcome to follow me, share Android dry goods and exchange Android technology.
If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

Topics: Android Back-end