Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package com.patchself.compose.sample.hyperspace

import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap
import kotlinx.coroutines.isActive
import kotlin.random.Random

/**
* Hyperspace animation composable that renders a starfield effect
*
* @param state Current animation state (Cruise, Warp, or Deceleration)
* @param modifier Modifier for the canvas
* @param starCount Number of stars to render
* @param baseSpeed Base speed multiplier for star movement
* @param backgroundColor Background color
*/
@Composable
fun HyperspaceAnimation(
state: HyperspaceState,
modifier: Modifier = Modifier,
starCount: Int = 200,
baseSpeed: Float = 1f,
backgroundColor: Color = Color.Black
) {
// Animation progress for smooth transitions
val targetSpeed = when (state) {
HyperspaceState.Cruise -> 1f
HyperspaceState.Warp -> 15f
HyperspaceState.Deceleration -> 1f
}

val animatedSpeed by animateFloatAsState(
targetValue = targetSpeed * baseSpeed,
animationSpec = tween(
durationMillis = when (state) {
HyperspaceState.Warp -> 1500 // Quick acceleration
HyperspaceState.Deceleration -> 3000 // Slow deceleration
else -> 1000
},
easing = when (state) {
HyperspaceState.Warp -> FastOutSlowInEasing
HyperspaceState.Deceleration -> LinearOutSlowInEasing
else -> LinearEasing
}
), label = "speed"
)

// Star trail length based on speed
val targetTrailLength = when {
animatedSpeed > 10f -> 80f
animatedSpeed > 5f -> 40f
else -> 5f
}

val animatedTrailLength by animateFloatAsState(
targetValue = targetTrailLength,
animationSpec = tween(1000), label = "trail"
)

// Initialize stars
val stars = remember {
List(starCount) {
Star(
x = 0f,
y = 0f,
z = Random.nextFloat() * 2f + 0.5f,
speed = Random.nextFloat() * 0.5f + 0.5f
)
}
}

// Continuous redraw for animation
LaunchedEffect(Unit) {
while (isActive) {
withFrameNanos { }
}
}

Canvas(modifier = modifier.fillMaxSize()) {
val centerX = size.width / 2f
val centerY = size.height / 2f
val maxDistance = kotlin.math.sqrt(centerX * centerX + centerY * centerY)

// Draw background
drawRect(backgroundColor)

// Update and draw each star
stars.forEach { star ->
// Initialize star position if needed
if (star.x == 0f && star.y == 0f) {
star.reset(centerX, centerY)
}

// Calculate direction from center
val dx = star.x - centerX
val dy = star.y - centerY
val distance = kotlin.math.sqrt(dx * dx + dy * dy)

// Normalize direction (avoid division by zero)
val nx = if (distance > 0) dx / distance else 0f
val ny = if (distance > 0) dy / distance else 0f

if (distance > 0) {
// Move star outward
val moveSpeed = animatedSpeed * star.speed * 0.5f
star.x += nx * moveSpeed
star.y += ny * moveSpeed

// Update z position for depth effect
star.z = kotlin.math.max(0.1f, star.z - moveSpeed * 0.01f)
}

// Get current position accounting for depth
val currentPos = star.getOffset(centerX, centerY)

// Reset star if it goes off screen
if (currentPos.x < -50 || currentPos.x > size.width + 50 ||
currentPos.y < -50 || currentPos.y > size.height + 50) {
star.reset(centerX, centerY)
} else {
// Calculate previous position for trail effect
val trailDistance = animatedTrailLength * star.speed
val prevX = star.x - nx * trailDistance
val prevY = star.y - ny * trailDistance
val prevZ = kotlin.math.min(3f, star.z + trailDistance * 0.01f)

val prevScale = 1f / prevZ
val prevPos = Offset(
centerX + (prevX - centerX) * prevScale,
centerY + (prevY - centerY) * prevScale
)

val starSize = star.getSize()
val alpha = (1f - (distance / maxDistance)).coerceIn(0f, 1f) * star.color.alpha

// Draw star as line (trail) or point based on speed
if (animatedTrailLength > 10f) {
// Draw as line (warp effect)
drawLine(
color = star.color.copy(alpha = alpha * 0.6f),
start = prevPos,
end = currentPos,
strokeWidth = starSize.coerceIn(0.5f, 3f),
cap = StrokeCap.Round
)
// Draw brighter point at the end
drawCircle(
color = star.color.copy(alpha = alpha),
radius = starSize,
center = currentPos
)
} else {
// Draw as point (cruise mode)
drawCircle(
color = star.color.copy(alpha = alpha),
radius = starSize,
center = currentPos
)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.patchself.compose.sample.hyperspace

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.patchself.compose.navigator.PageController
import com.patchself.compose.sample.R

/**
* Demo page for the hyperspace animation
*/
class HyperspacePage : PageController() {

override fun getId() = R.id.HyperspacePage

@Composable
override fun ScreenContent() {
var currentState by remember { mutableStateOf<HyperspaceState>(HyperspaceState.Cruise) }

Box(modifier = Modifier.fillMaxSize()) {
// Hyperspace animation background
HyperspaceAnimation(
state = currentState,
modifier = Modifier.fillMaxSize()
)

// Top bar
TopAppBar(
title = { Text(text = "Hyperspace Animation") },
navigationIcon = {
IconButton(onClick = { navigateBack() }) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = "Back",
tint = Color.White
)
}
},
backgroundColor = Color.Black.copy(alpha = 0.5f),
contentColor = Color.White,
elevation = 0.dp
)

// Control buttons at bottom
Column(
modifier = Modifier
.align(Alignment.BottomCenter)
.fillMaxWidth()
.background(Color.Black.copy(alpha = 0.5f))
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Text(
text = when (currentState) {
HyperspaceState.Cruise -> "巡航状态 (Cruise Mode)"
HyperspaceState.Warp -> "超光速状态 (Warp Mode)"
HyperspaceState.Deceleration -> "减速中 (Decelerating)"
},
color = Color.White,
style = MaterialTheme.typography.h6
)

Row(
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Button(
onClick = { currentState = HyperspaceState.Cruise },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (currentState == HyperspaceState.Cruise)
Color.Cyan else Color.DarkGray
)
) {
Text("巡航\nCruise")
}

Button(
onClick = { currentState = HyperspaceState.Warp },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (currentState == HyperspaceState.Warp)
Color.Red else Color.DarkGray
)
) {
Text("超光速\nWarp")
}

Button(
onClick = { currentState = HyperspaceState.Deceleration },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (currentState == HyperspaceState.Deceleration)
Color.Yellow else Color.DarkGray
)
) {
Text("减速\nSlow Down")
}
}

Text(
text = "点击按钮切换动画状态",
color = Color.White.copy(alpha = 0.7f),
style = MaterialTheme.typography.caption
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.patchself.compose.sample.hyperspace

/**
* Represents the different states of the hyperspace animation
*/
sealed class HyperspaceState {
/**
* Cruise mode - stars move slowly from center to edges as points
*/
object Cruise : HyperspaceState()

/**
* Warp/Hyperspace mode - stars stretch into lines moving rapidly
*/
object Warp : HyperspaceState()

/**
* Deceleration mode - transitioning from Warp back to Cruise
*/
object Deceleration : HyperspaceState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.patchself.compose.sample.hyperspace

import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import kotlin.random.Random

/**
* Represents a single star in the hyperspace animation
*/
data class Star(
var x: Float,
var y: Float,
var z: Float, // Depth for 3D effect
var speed: Float,
val baseSize: Float = Random.nextFloat() * 2f + 1f,
val color: Color = Color.White.copy(alpha = Random.nextFloat() * 0.5f + 0.5f)
) {
/**
* Get the current position offset accounting for depth
*/
fun getOffset(centerX: Float, centerY: Float): Offset {
val scale = 1f / z
return Offset(
centerX + (x - centerX) * scale,
centerY + (y - centerY) * scale
)
}

/**
* Get the size of the star accounting for depth
*/
fun getSize(): Float {
return baseSize / z
}

/**
* Reset star to center with new random properties
*/
fun reset(centerX: Float, centerY: Float) {
x = centerX + (Random.nextFloat() - 0.5f) * 100f
y = centerY + (Random.nextFloat() - 0.5f) * 100f
z = Random.nextFloat() * 2f + 0.5f
speed = Random.nextFloat() * 0.5f + 0.5f
}
}
14 changes: 14 additions & 0 deletions sample/src/main/java/com/patchself/compose/sample/ui/HomePage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.patchself.compose.navigator.PageController
import com.patchself.compose.sample.R
import com.patchself.compose.sample.hyperspace.HyperspacePage

class HomePage : PageController() {

Expand Down Expand Up @@ -56,6 +57,19 @@ class HomePage : PageController() {
text = "Scroll Page",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
FloatingActionButton(onClick = {
navigateTo(HyperspacePage())
}, Modifier.align(Alignment.CenterHorizontally)) {
Image(Icons.Filled.ArrowForward,
colorFilter = ColorFilter.tint(Color.White),
contentDescription = ""
)
}
Spacer(modifier = Modifier.height(Dp(15f)))
Text(
text = "Hyperspace Animation",
modifier = Modifier.align(Alignment.CenterHorizontally)
)
if (fromThirdPage){
Spacer(modifier = Modifier.height(Dp(25f)))
Text(
Expand Down
1 change: 1 addition & 0 deletions sample/src/main/res/values/ids.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<item name="HomePage" type="id"/>
<item name="SecondPage" type="id"/>
<item name="ThirdPage" type="id"/>
<item name="HyperspacePage" type="id"/>
</resources>