Skip to content

Instantly share code, notes, and snippets.

@equationl
Last active November 9, 2022 06:27
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save equationl/5c3a86d506ed8b0333230eeed191854d to your computer and use it in GitHub Desktop.
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/
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()}"
}
)
}
}
}
}
}
}
/**
*
* 增加星空背景
*
* @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