Creating a Parallax Background in FlutterHow to build a tab navigator with parallax background in FlutterKenneth ReillyBlockedUnblockFollowFollowingMay 13Screen capture of iOS simulator running the flutter-parallax-nav-demo projectIntroductionIn this article we will look at the process of creating a scrollable tab navigation layout with synchronized parallax background using Dart and Flutter.
Dart with Flutter is a great combination for mobile development.
Having built user interfaces in everything from Silverlight and WPF to dozens of various desktop, web, and mobile platforms, I find that Flutter provides a great environment for quickly building rich mobile app experiences.
If you don’t already have the Flutter SDK, head over to the installation page and follow the instructions for your operating system.
If you’d like a copy of the source code for the demo project used in this article, check it out here.
Getting StartedLet’s take a look at the project structure and its definition file, pubspec.
yaml:A default pubspec.
yaml with added event_bus package and image assetsIn the project definition file pubspec.
yaml, the event_bus dependency has been added.
An Event Bus allows listeners to subscribe to events and for publishers to fire these events.
For more information about the event_bus package for Dart and how it works, check it out here.
Also in this file, the background image is included as an image asset under assets.
This will allow the image file to be referenced and displayed from within the application.
Main Application ContainerNext, let’s check out the main entry file of the application, lib/main.
dart:The application entry file lib/main.
dart with top-level widget builderThis file defines the root application component ParallaxDemo which is a StatelessWidget that returns a MaterialApp component with our title of “Parallax BG”, some default theme data, and the home AppContainer widget.
In this project, definitions for the “screens” are just the letters A through E, which we render as both the tab icon and screen content for simplicity.
Note that our main() function has been defined with async to allow calling the asynchronous method SystemChrome.
setPrefferedOrientations(), which locks the device orientation to DeviceOrientation.
This was chosen for convenience only, and could be modified for any purpose.
Now let’s check out the AppContainer, in components/app-container.
dart:The AppContainer class with Background and NavContainer stacked on the z-axisThis class also extends StatelessWidget since it’s just a container with no state of its own.
It takes the screens parameter which is our list of letters A to E.
Notice that the builder returns WillPopScope, which allows this widget to capture the Android back button and then call the NavigationBus within the onWillPop handler to forward events upstream.
Scrollable BackgroundThe next component class we’ll check out is the Background widget, which is located in lib/components/background.
dart:The Background class with various properties and initializersUnlike the class files we examined previously, this Background class extends a StatefulWidget to support local mutable state properties and the ability for events to trigger a redraw using setState().
There is an aspectRatio property which I have arbitrarily set to 16/9 but could be any aspect ratio depending on the images you choose to display and the device sizes and orientations that you intend to support in your application.
Also, there is an initState() method override that registers an event listener on the same NavigationBus that showed up in the last class.
The purpose of this listener is to cause this widget to re-draw itself when a new Animation object has become available on the event bus.
Since the scrolling tab animation isn’t likely to exist at the time the Background is first created, this ensures that it will receive a reference to the updated Animation once it has been created by the scrolling tab controller.
The Background class showing the build methodThe build method for the Background class grabs a reference to the current instance of Animation available on the NavController, passes that to the AnimatedBuilder, and then calculates an offset for Alignment which is 10% of the value of the animation.
In this example with five screens, the value for animation.
value will range from 0.
0 through 4.
0 which we in turn map from 0.
0 through 0.
4 to pass along to the Alignment widget, which uses this value as a percentage to offset the child of OverflowBox.
This works great for our example project, but in real-world applications, the actual positioning of the image will depend on its size and the aspectRatio used for the parallax effect.
Navigation Event BusNext up is our lone service file, services/navigation-bus.
dart:The file services/navigation-bus.
dart with event types and NavigationBus classJust above the NavigationBus class there are a few one-liner class definitions for event types, which are used when forwarding events to specific listeners based on event type.
An instance of EventBus is created and there is a getter for animation, which returns an AlwaysStoppedAnimation unless there is an instance of TabController available, at which point it will start to return that controller’s animation, to allow other widgets to drive themselves off the tab navigator’s changing animation value.
There are also methods for registering new listeners.
The first of these, registerNavigationListener(), is not used in this project but it provides an example of how the EventBus can be used to track navigation events app-wide to handle all sorts of things, from triggering background network requests to recording analytics or anything else.
The other event listener, registerControllerAttachedListener, is used by the Background class to subscribe to events of type ControllerAttachedEvent.
When a TabController is registered on the bus, a reference to it is saved and animation events from it are are forwarded to the event bus via the handler onUpdateTabAnimation.
Also, a ControllerAttachedEvent is fired down the bus to notify any related components to grab a new copy of the Animation, as in the case with the Background which begins in a fixed position only, and then starts to follow the tab navigator once the everything is up and running.
Navigation Container UXThe navigation components are split into three files: the parent container, a widget for the screen content itself, and a widget for the tab navigation icons.
We’ll start with the main navigation file, components/nav-container.
dart:The NavContainer class with various state propertiesThe NavContainer serves as the parent container for displaying both the application content itself and the bottom tabs.
There is a children property and local reference for TabController and Animation.
The _tabs getter starts with the children property (letters A through E) and maps it to a List<Tab> with instances of NavTabIcon, which highlights the currently selected tab.
The NavContainer class showing _routes and various overridesAlso within this class is a getter which takes the same children property and maps is to a List<NavRouteView> which display our letters A through E and apply some cool navigation effects during page transitions.
The override for initState() creates a TabController and registers it on the NavigationBus.
Also, there is a dispose() method which explicitly disposes the controller instance when the navigation container has being disposed of.
The NavContainer class with build methodWe won’t dig too much further into this class, but it’s a pretty standard-fare implementation of the usual columns, containers, and decorations.
There are a few conditional branches to handle rendering the tab bar differently on iOS than Android, which helps get the tabs up out of the rounded corners and the bottom drawer handle on iPhone X which it would normally interfere with.
Next up is the file lib/components/nav-route-view.
dart:The animated widget NavRouteView with some calculationsHere we have our NavRouteView, which is an AnimatedWidget that applies various transforms to the route as it flies in and out of the screen.
The method scaleDouble is a linear transform which takes the animation value and scales it down proportionally to a smaller value, in this case to 80% of the original.
Notice the formula for calculating scale which determines how the container will be drawn on the screen depending on whether it is being swiped into or out of view, and where it is in relation to the visible portion of the screen, which is handled by subtracting the widget’s index from the animation value and then performing a group of conditional tests on the result.
Last but not least we’ll take a look at lib/components/nav-tab-icon.
dart:NavTabIcon with calculations for rendering an iconThe NavTabIcon class is similar to NavRouteView in that it updates how it should look based on the current state of the animation passed into it (our tab navigator animation value).
The code which accomplishes this could be made into some abstract feature instead of duplicated between these classes, but this does give some flexibility in experimenting with different values for each.
This widget renders the provided icon, which in this example is simply one of the same letters A through E with a text style applied to it.
Again we see a platform check to help alleviate any issues that arise from drawing on top of native iOS phone components.
The opacity values are clamped between 0.
26 and 0.
84 in this widget, to preventing the icons from being either totally opaque or transparent, giving them a more subtle look.
ConclusionFlutter provides a powerful set of features that take full advantage of the Dart language for creating modern native applications, with incredible animations and transitions that render seamlessly with excellent performance.
This project illustrates a few of the concepts within Flutter that make it possible to build highly complex, interactive mobile applications with clean source files, free from giant lists of dependencies and spaghetti code.
Using an Event Bus and a few standard components, we were able to wire up what would otherwise require yet another dependency package, in a way that is easy to maintain and unlikely to break for no apparent reason when you least expect it.
These concepts can be applied to any kind of animation or control gesture that you can think of, limited only by imagination.
I hope you enjoyed this article and found it useful.
The full source code for the project is available here, and feel free to contact me with any questions or suggestions.
Thanks for reading and good luck with Flutter!.