Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Parallax effect with Jetpack Compose
@Composable
fun ParallaxScreen(modifier: Modifier = Modifier) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
var data by remember { mutableStateOf<SensorData?>(null) }
DisposableEffect(Unit) {
val dataManager = SensorDataManager(context)
dataManager.init()
val job = scope.launch {
dataManager.data
.receiveAsFlow()
.onEach { data = it }
.collect()
}
onDispose {
dataManager.cancel()
job.cancel()
}
}
Box(modifier = modifier) {
ParallaxView(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.Center),
multiPlier = 20,
data = data
)
}
}
// For changing offset values, it is always preferred to use .offset { } instead of .offset()
// as offset {..} is implemented to avoid recomposition during the offset changes
@Composable
fun ParallaxView(
modifier: Modifier = Modifier,
depthMultiplier: Int = 20,
data: SensorData?
) {
val roll by derivedStateOf { (data?.roll ?: 0f) * depthMultiplier }
val pitch by derivedStateOf { (data?.pitch ?: 0f) * depthMultiplier }
Box(modifier = modifier) {
// Glow Shadow
// Has quicker offset change and in opposite direction to the Image Card
Image(
painter = painterResource(id = drawable.beach),
modifier = Modifier
.offset {
IntOffset(
x = -(roll * 1.5).dp.roundToPx(),
y = (pitch * 2).dp.roundToPx()
)
}
.width(256.dp)
.height(356.dp)
.align(Alignment.Center)
.blur(radius = 24.dp, edgeTreatment = BlurredEdgeTreatment.Unbounded),
contentDescription = null,
contentScale = ContentScale.FillHeight,
)
// Edge (used to give depth to card when tilted)
// Has slightly slower offset change than Image Card
Box(
modifier = Modifier
.offset {
IntOffset(
x = (roll * 0.9).dp.roundToPx(),
y = -(pitch * 0.9).dp.roundToPx()
)
}
.width(300.dp)
.height(400.dp)
.align(Alignment.Center)
.background(
color = Color.White.copy(alpha = 0.3f),
shape = RoundedCornerShape(16.dp)
),
)
// Image Card
// The image inside has a parallax shift in the opposite direction
Image(
painter = painterResource(id = drawable.beach),
modifier = Modifier
.offset {
IntOffset(
x = roll.dp.roundToPx(),
y = -pitch.dp.roundToPx()
)
}
.width(300.dp)
.height(400.dp)
.align(Alignment.Center)
.clip(RoundedCornerShape(16.dp)),
contentDescription = null,
contentScale = ContentScale.FillHeight,
alignment = BiasAlignment(
horizontalBias = (roll * 0.005).toFloat(),
verticalBias = 0f,
)
)
}
}
class SensorDataManager (context: Context): SensorEventListener {
private val sensorManager by lazy {
context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
}
fun init() {
Log.d("SensorDataManager", "init")
val accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY)
val magnetometer = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI)
sensorManager.registerListener(this, magnetometer, SensorManager.SENSOR_DELAY_UI)
}
private var gravity: FloatArray? = null
private var geomagnetic: FloatArray? = null
val data: Channel<SensorData> = Channel(Channel.UNLIMITED)
override fun onSensorChanged(event: SensorEvent?) {
if (event?.sensor?.type == Sensor.TYPE_GRAVITY)
gravity = event.values
if (event?.sensor?.type == Sensor.TYPE_MAGNETIC_FIELD)
geomagnetic = event.values
if (gravity != null && geomagnetic != null) {
var r = FloatArray(9)
var i = FloatArray(9)
if (SensorManager.getRotationMatrix(r, i, gravity, geomagnetic)) {
var orientation = FloatArray(3)
SensorManager.getOrientation(r, orientation)
data.trySend(
SensorData(
roll = orientation[2],
pitch = orientation[1]
)
)
}
}
}
fun cancel() {
Log.d("SensorDataManager", "cancel")
sensorManager.unregisterListener(this)
}
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
}
data class SensorData(
val roll: Float,
val pitch: Float
)
@thebehera
Copy link

thebehera commented Nov 15, 2022

SensorManager.getOrientation can run into gimbal lock issues, so there must be some kind of limit to this before it fails

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment