Portal Berita dan Informasi Terbaik di Indonesia

Parallax Effect with SensorManager using Jetpack Compose | by Suraj Sau | May, 2022

Alessio Soggetti・Unsplash

Recently, an implementation of stunning Parallax effect in SwiftUI by Philip Davis has been making rounds in the Android Community too.

So, I decided to try implementing the same using Jetpack Compose instead. The results turned out pretty decent and also well received by our lovely Android Twitter Community. 😁

The following explanation is completely based on this gist.

Fetching Sensor data

The @Composable views will be repositioned according to the orientation of the device. And we’ll be needing the orientation values along the ‘Pitch’ and the ‘Roll’ axes.

I’ve referred to this StackOverflow reply for my implementation of fetching the Pitch and Roll values.

Listening to sensor changes to obtain Pitch and Roll

Register SensorManager listeners

Using DisposableEffect we’ll be initialising the SensorManager and also register listeners for the GRAVITY & MAGNETIC_FIELD sensors.

Finally the Pitch and Roll are received within the @Composable using Channel.receiveAsFlow().collect().

Initialising SensorManager in a DisposableEffect
Wrapper for our SensorManager implementation

One may say that we could’ve LaunchedEffect instead since we’re only collecting our sensor’s data Flow. I’ll explain why I went with a DisposableEffect in the end (it has something to do with onDispose{}).

Now, once we have the Pitch and Roll values from our sensor, we’ll now be looking into how we can reposition various @Composable views to achieve the parallax effect. Philip Davis has himself graciously hinted at his implementation:

  • repositioning the Image Card
  • repositioning the Glow Shadow (reverse of the Card). I used Philip’s Glow Shadow implementation as reference for this.
  • showing a subtle Card edge when tilted, for the pronounced 3D effect
  • adding a parallax to Image inside (reverse of the Card)
View repositioning directions based on title direction

Image Card

Image Card implementation

[1] we’re using Modifier.offset to reposition our @Composable views.

[2] Roll is along the y-axis while Pitch is along the x-axis. So, the x-offset will be decided by the roll while y-offset by the pitch.

[3] We bring about the inside Image’s parallax effect by adjusting the horizontalBias of @Composable Image() alignment parameter.

Image Card’s motion along the devices tilted orientation

Glow Shadow

Glow Shadow implementation

[1] Glow Shadow’s offset will reposition quicker than the Image Card and in the opposite direction.

[2] It’s size will be smaller than the Image Card.

  • When the device is kept on a Flat surface (all orientation values close to 0), the glow shadow should not be visible.
  • The darker portion of shadows are generally smaller than the actual object.

[3] Instead of a traditional shadow, use Modifier.blur() to make the Image Card appear translucent which is letting in light onto the surface.

Glow Shadow’s motion along with the Image Card

Card Edge

Card Edge implementation

[1] Card Edge’s offset repositioning will be slightly flower than Image Card.

  • The edge shouldn’t be visible when kept on a Flat surface.
  • The edge should ‘just’ be visible when the device is tilted to give the impression of a thin 3D card.

[2] It’s size will be same of the Image Card.

Card Edge’s motion along with the Image Card

Unregister cancel fetching of sensor data in DisposableEffect.onDispose{..}

We must prevent unnecessary leaking of SensorManager when this view is removed from the composition. That is the reason why I used DisposableEffect instead of LaunchedEffect since we get this convenient onDispose{} callback where we can clean up any unnecessary data upon leaving composition.

Use the right Modifier.offset method

Jetpack Compose provides two kinds of Offset modifiers:

  • .offset(x: Dp = 0.dp, y: Dp = 0.dp)
  • .offset(offset: Density.() -> IntOffset)

While these do the same thing i.e., set positional offset to the @Composable view, IF we are going to use dynamic offsets (for example, offsets changed due to certain animation or user action etc.) instead of fixed offsets, we should always use .offset(offset: Density.() -> IntOffset i.e., the lambda version. The reason being, the latter method avoids recomposition when the offset is changing values, thus significantly improving the performance. Even the documentation says,

This modifier is designed to be used for offsets that change, possibly due to user interactions. It avoids recomposition when the offset is changing, and also adds a graphics layer that prevents unnecessary redrawing of the context when the offset is changing.

While this isn’t the most performance efficient implementation, hope this can help as a handy reference for the same.

Jetpack Compose and Swift UI are the best tools to realise high quality visual effects in such a short period time. I certainly believe that these two platforms will empower Mobile Developers to create beautiful experiences for the users. 🥳

Source by [author_name]

Leave a Reply

Your email address will not be published.