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!