Flutter Smooth Parallax Effect For Login and Intro Screens

Have you seen Apple iOS’s parallax wallpaper? If you enable “Perspective Zoom” in your iOS’s wallpaper setting, you’ll find your HomeScreen will respond visually as per your device’s tilt/position. It’s simple eye trickery but it looks impressive.

Here is an example of what we’re gonna build today.

NOTE: All the images in this post are used within FAIR USE and actual files are not distributed.

Let’s start..

Here we’ll be using Flutter’s official sensors plugin to get device’s tilt/rotation.
To install simply put below as your dependency and run a “pub get” in your project folder.

sensors: ^0.4.2+6

Note: Please check and use the latest version. I’ve used version above when writing this post.

Now we’ll be needing two images one for starry background and one for planet. Make sure planet’s image is transparent as we’re going stack up this image on top of background image.

We’ll be using Stack widget to place the image. And Positioned widget to position the images inside the stack.

For device’s tilt/rotation detection we’ll use Accelerometer Events to get the acceleration on x, y Axis.

Let’s code..

class PerspectiveZoomDemo extends StatefulWidget {
PerspectiveZoomDemo({
Key key,
}) : super(key: key);

@override
_PerspectiveZoomDemoState createState() => _PerspectiveZoomDemoState();
}

class _PerspectiveZoomDemoState extends State<PerspectiveZoomDemo> {
AccelerometerEvent acceleration;
StreamSubscription<AccelerometerEvent> _streamSubscription;

int planetMotionSensitivity = 4;
int bgMotionSensitivity = 2;

@override
void initState() {
_streamSubscription = accelerometerEvents.listen((AccelerometerEvent event) {
setState(() {
acceleration = event;
});
});
super.initState();
}

@override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
Positioned(
top: acceleration.y * bgMotionSensitivity,
bottom: acceleration.y * -bgMotionSensitivity,
right: acceleration.x * -bgMotionSensitivity,
left: acceleration.x * bgMotionSensitivity,
child: Align(
child: Image.asset(
"assets/images/bg.jpg",
height: 1920,
fit: BoxFit.fitHeight,
),
),
),
Positioned(
top: acceleration.y * planetMotionSensitivity,
bottom: acceleration.y * -planetMotionSensitivity,
right: acceleration.x * -planetMotionSensitivity,
left: acceleration.x * planetMotionSensitivity,
child: Align(
child: Image.asset(
"assets/images/earth_2.png",
width: 250,
),
),
),
],
),
),
);
}
}

We’ve subscribed to device’s accelerometer events and we’re changing position of our background and planet images based on a multiplier we’re calling bgMotionSensitivity & planetMotionSensitivity.

Why bgMotionSensitivity is lower than planetMotionSensitivity?

Answer: Well thats how the parallax effect works, objects that are far away from you moves slowly compare to object that are close to you.

Planet and Background is responding well to device’s motion but…

if you try to run it you’ll find it’s quite shaky and stutters a lot.
So what we’ve missed here?

Problem here is events from accelerometer is so rapid and values are changing too fast here so whenever there is sudden leap in values our animation stutters.
We need to add some kind of transition delay, to fill in those big leap in values. Luckily flutter provides a widget called AnimatedPositioned which can be used in Stack widget.

AnimatedPositioned(
duration: Duration(milliseconds: 250),
top: acceleration.y * bgMotionSensitivity,
bottom: acceleration.y * -bgMotionSensitivity,
right: acceleration.x * -bgMotionSensitivity,
left: acceleration.x * bgMotionSensitivity,
child: Align(
child: Image.asset(
"assets/images/bg.jpg",
height: 1920,
fit: BoxFit.fitHeight,
),
),
),
AnimatedPositioned(
duration: Duration(milliseconds: 250),
top: acceleration.y * planetMotionSensitivity,
bottom: acceleration.y * -planetMotionSensitivity,
right: acceleration.x * -planetMotionSensitivity,
left: acceleration.x * planetMotionSensitivity,
child: Align(
child: Image.asset(
"assets/images/earth_2.png",
width: 250,
),
),
),

We’ve replaced Positioned with AnimatedPositioned and specified some amount of delay in millisecond to create a smooth transitional effect.

Responds smoothly like it’s floating inside the screen.
This can be used as nice background for login or intro screens.

That’s all :)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store