Josh Software

Flutter Interactive Motion Backgrounds – Josh Software

Flutter

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 because of Flutter Interactive Motion Backgrounds, apps built with flutter. 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.

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

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

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

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

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

https://joshsoftware.digital/flutter/create-a-new-flutter-app-for-your-business-i/

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.<em>black</em>,
      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,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Explanation:

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

Why bgMotionSensitivity is lower than planetMotionSensitivity?

Answer: Well that’s how the parallax effect works, objects that are far away from your move slowly compared to an object that is close to you.

Results:

Planet and Background are responding well to the 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 & Solution:

The problem here is events from the accelerometer are so rapid and values are changing too fast here so whenever there is a 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.

Applied Solution:

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,
      ),
    ),
  ),

Explanation:

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

Results:

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

That’s all 🙂

Source code available here.