Last active
November 9, 2022 06:27
-
-
Save equationl/5c3a86d506ed8b0333230eeed191854d to your computer and use it in GitHub Desktop.
A starry sky background by Jetpack compose, see this: https://juejin.cn/post/7143505521218158629/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class PreviewActivity : ComponentActivity() { | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContent { | |
StarrySkyTheme { | |
// A surface container using the 'background' color from the theme | |
Surface( | |
modifier = Modifier.fillMaxSize(), | |
color = MaterialTheme.colors.background | |
) { | |
Column( | |
Modifier | |
.fillMaxSize() | |
.drawStarrySkyBg( | |
starNum = 50, | |
meteorRadian = 2.356194490192345, // 135 度 | |
//meteorStrokeWidth = 30f, | |
//meteorTime = 30000, | |
showDebugInfo = true | |
), | |
verticalArrangement = Arrangement.Center, | |
horizontalAlignment = Alignment.CenterHorizontally | |
) { | |
var text by remember { mutableStateOf("Hello equationl \n at starry sky\n${System.currentTimeMillis()}") } | |
Text( | |
text = text, | |
color = Color.White, | |
fontSize = 32.sp, | |
modifier = Modifier.clickable { | |
text = "Hello equationl \n at starry sky\n${System.currentTimeMillis()}" | |
} | |
) | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* 增加星空背景 | |
* | |
* @param seed 随机种子,用于随机生成星星和流星 | |
* @param background 星空背景颜色 | |
* @param starNum 生成的星星总数 | |
* @param starColorList 星星的颜色列表,随机从中获取颜色用于填充星星 | |
* @param starSizeList 星星的缩放尺寸列表,随机从中获取值用于缩放星星,大于 1 表示放大,小于 1 表示缩小 | |
* @param meteorTime 流星存在持续时间(ms) | |
* @param meteorScaleTime 生成流星间隔时间(ms) | |
* @param meteorVelocity 流星飞行速度(ms) | |
* @param meteorStrokeWidth 流星线宽(px) | |
* @param meteorRadian 流星轨迹弧度 | |
* @param meteorLength 流星长度(px),如果小于等于0则表示使用无限长度 | |
* | |
* */ | |
fun Modifier.drawStarrySkyBg( | |
seed: Long = -1, | |
background: Color = Color.Black, | |
starNum: Int = 20, | |
starColorList: List<Color> = listOf(Color(0x99CCCCCC), Color(0x99AAAAAA), Color(0x99777777)), | |
starSizeList: List<Float> = listOf(0.8f, 0.9f, 1.2f), | |
meteorColor: Color = Color.White, | |
meteorTime: Int = 1500, | |
meteorScaleTime: Int = 3000, | |
meteorVelocity: Float = 10f, | |
meteorRadian: Double = 0.7853981633974483, // 45度 | |
meteorLength: Float = 500f, | |
meteorStrokeWidth: Float = 1f, | |
showDebugInfo: Boolean = false | |
) : Modifier = composed { | |
val deltaMeteorAnim = rememberInfiniteTransition() | |
val meteorTimeAnim by deltaMeteorAnim.animateFloat( | |
initialValue = 0f, | |
targetValue = 300f, // 这个值其实可以根据时间、速度、指定长度、以及当前绘制区域可用大小计算出来,但是我懒得算来,就直接写死一个比较大的值来 | |
animationSpec = infiniteRepeatable( | |
animation = tween(durationMillis = meteorTime, delayMillis = meteorScaleTime, easing = LinearEasing) | |
) | |
) | |
val meteorAlphaAnima by deltaMeteorAnim.animateFloat( | |
initialValue = 0f, | |
targetValue = 1000f, // 透明度的动画时长应该是整体动画的 1/10 。这里直接使用1000作为目标值 | |
animationSpec = infiniteRepeatable( | |
animation = tween(durationMillis = meteorTime, delayMillis = meteorScaleTime, easing = LinearEasing) | |
) | |
) | |
drawWithCache { | |
val random = Random(seed) | |
val startInfoList = mutableListOf<StarInfo>() | |
// 添加星星数据 | |
for (i in 0 until starNum) { | |
val sizeScale = starSizeList.random(random) | |
startInfoList.add( | |
StarInfo( | |
Offset(random.nextDouble(size.width.toDouble()).toFloat(), random.nextDouble(size.height.toDouble()).toFloat()), | |
starColorList.random(random), | |
size.width/200 * sizeScale | |
) | |
) | |
} | |
val cosAngle = cos(meteorRadian).toFloat() | |
val sinAngle = sin(meteorRadian).toFloat() | |
val safeDistanceStandard = (size.width / 10).toInt() | |
val safeDistanceVertical = (size.height / 10).toInt() | |
var currentStartX = 0f | |
var currentStartY = 0f | |
var currentEndX = 0f | |
var currentEndY = 0f | |
var currentLength = 0f | |
var startX = 0 | |
var startY = 0 | |
onDrawBehind { | |
// 绘制背景 | |
drawRect(color = background) | |
// 绘制星星 | |
for (star in startInfoList) { | |
drawCircle(color = star.color, center = star.offset, radius = star.radius) | |
} | |
// 计算流星坐标并绘制流星 | |
if (currentStartX <= size.width && | |
currentStartY <= size.height && | |
currentStartX >= 0 && | |
currentStartY >= 0 | |
) { // 只有当流星起点还在绘制范围内时才继续计算以及绘制流星 | |
// 只有未达到目标长度,且不是无限长度才实时计算当前长度 | |
if (currentLength != meteorLength && meteorLength > 0) { | |
currentLength = sqrt( | |
(currentEndX - currentStartX).pow(2) + (currentEndY - currentStartY).pow(2) | |
) | |
} | |
// 计算当前起点坐标 | |
currentStartX = startX + meteorVelocity * meteorTimeAnim * cosAngle | |
currentStartY = startY + meteorVelocity * meteorTimeAnim * sinAngle | |
// 如果是无限长度或长度未达到目标长度,则开始增长长度,具体表现为计算终点坐标时,速度是起点的两倍 | |
if (meteorLength <= 0 || currentLength < meteorLength) { | |
currentEndX = startX + meteorVelocity * meteorTimeAnim * 2 * cosAngle | |
currentEndY = startY + meteorVelocity * meteorTimeAnim * 2 * sinAngle | |
} | |
else { // 已达到目标长度,直接用起点坐标加上目标长度即可得到终点坐标 | |
currentLength = meteorLength | |
currentEndX = currentStartX + meteorLength * cosAngle | |
currentEndY = currentStartY + meteorLength * sinAngle | |
} | |
if (meteorTimeAnim != 0f) { | |
// 绘制流星 | |
drawLine( | |
color = meteorColor, | |
start = Offset(currentStartX, currentStartY), | |
end = Offset(currentEndX, currentEndY), | |
strokeWidth = meteorStrokeWidth, | |
alpha = (meteorAlphaAnima / 100).coerceAtMost(1f) | |
) | |
} | |
} | |
// 本轮流星动画结束,重新初始化参数 | |
if (meteorTimeAnim == 0f) { | |
// 限制生成的起点坐标应该在安全区域内 | |
startX = random.nextInt(safeDistanceStandard, size.width.toInt() - safeDistanceStandard) | |
startY = random.nextInt(safeDistanceVertical, size.height.toInt() - safeDistanceVertical) | |
currentStartX = 0f | |
currentStartY = 0f | |
currentEndX = 0f | |
currentEndY = 0f | |
currentLength = 0f | |
} | |
// 测试信息 | |
if (showDebugInfo) { | |
val paint = android.graphics.Paint().apply { | |
color = android.graphics.Color.WHITE | |
textSize = 12.sp.toPx() | |
} | |
drawIntoCanvas { | |
it.nativeCanvas.drawText( | |
"$meteorTimeAnim", | |
50f, | |
100f, | |
paint | |
) | |
it.nativeCanvas.drawText( | |
"currentStart = $currentStartX, $currentStartY", | |
50f, | |
150f, | |
paint | |
) | |
it.nativeCanvas.drawText( | |
"currentEnd = $currentEndX, $currentEndY", | |
50f, | |
200f, | |
paint | |
) | |
it.nativeCanvas.drawText( | |
"length = $currentLength", | |
50f, | |
250f, | |
paint | |
) | |
} | |
} | |
} | |
} | |
} | |
data class StarInfo( | |
val offset: Offset, | |
val color: Color, | |
val radius: Float | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment