Lottie: Add Complex Vector Animations to Mobile Apps
Lottie has turned the once‑cumbersome process of adding vector‑based animations into a few lines of code. Whether you’re building an iOS, Android, or cross‑platform app, Lottie lets you ship high‑quality motion without bloating your binary. In this guide we’ll explore how Lottie works under the hood, walk through three real‑world integrations, and share pro tips to keep your animations smooth and maintainable.
What is Lottie and Why It Matters
Lottie is an open‑source library created by Airbnb that parses Adobe After Effects animations exported as JSON via the Bodymovin plugin. Because the output is vector‑based, animations scale perfectly on any screen density and remain tiny—often under 100 KB compared to megabytes for video or GIF assets.
The library is available for iOS, Android, React Native, Flutter, and the web, making it a universal solution for mobile developers. It also supports runtime manipulation, so you can programmatically control speed, color, and playback direction.
Key Benefits
- Performance: Lottie renders animations on the GPU, which means low CPU usage and buttery‑smooth frames even on older devices.
- File Size: JSON files are typically 10‑20 × smaller than comparable raster sequences.
- Scalability: Being vector‑based, animations look crisp on any resolution without extra assets.
- Interactivity: You can pause, reverse, or scrub through an animation based on user input.
Pro tip: Always enable hardware acceleration in your Android manifest (`android:hardwareAccelerated="true"`) when using Lottie to avoid occasional rendering glitches on low‑end devices.
Getting Started: Installing Lottie
Installation varies by platform, but the process is straightforward. Below are the three most common setups.
Android (Kotlin)
- Add the Maven Central repository (if not already present) in
build.gradle:
repositories {
google()
mavenCentral()
}
- Include the Lottie dependency:
dependencies {
implementation "com.airbnb.android:lottie:6.3.0"
}
iOS (Swift)
- Using CocoaPods:
pod 'lottie-ios'
- Or Swift Package Manager (Xcode 13+):
dependencies: [
.package(url: "https://github.com/airbnb/lottie-ios.git", from: "4.2.0")
]
Flutter
In pubspec.yaml add the latest Lottie package.
dependencies:
flutter:
sdk: flutter
lottie: ^3.1.0
Pro tip: Pin the Lottie version you test against. New releases sometimes introduce breaking changes in JSON parsing.
Preparing Your Animation Asset
All Lottie animations start as a JSON file exported from After Effects using the Bodymovin plugin. Here’s a quick checklist before you ship the file.
- Keep the animation under 60 seconds to avoid large JSON payloads.
- Prefer shape layers over raster images; they stay vector and keep the file tiny.
- Turn off “Include Hidden Layers” to trim unnecessary data.
- Test the JSON in LottieFiles Preview to ensure it renders correctly.
Once you’re satisfied, download the .json file and place it in your project’s assets folder (e.g., src/main/res/raw for Android, Assets.xcassets for iOS, assets/ for Flutter).
Example 1: Adding a Loading Spinner on Android
A loading spinner is a classic use case—small, looped, and always visible. Below is a minimal implementation using Kotlin and Android’s ViewBinding.
Layout XML
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/loadingAnimation"
android:layout_width="150dp"
android:layout_height="150dp"
app:lottie_fileName="spinner.json"
app:lottie_loop="true"
app:lottie_autoPlay="true" />
</LinearLayout>
Activity Code
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Optional: control speed or start/stop programmatically
binding.loadingAnimation.speed = 1.5f // 1.5× faster
binding.loadingAnimation.playAnimation()
}
// Simulate network call
private fun fetchData() {
binding.loadingAnimation.visibility = View.VISIBLE
// ... network request ...
// on success:
binding.loadingAnimation.visibility = View.GONE
}
}
This example demonstrates the default auto‑play behavior and how you can tweak the playback speed at runtime. The animation loops indefinitely until you hide the view.
Example 2: Interactive Onboarding in iOS
Onboarding screens benefit from a narrative flow. By syncing animation progress with a scroll view, you can create a storytelling experience that feels native.
Storyboard Setup
- Drag a
UIViewonto the view controller and set constraints to fill the screen. - Add a
LottieAnimationView(via code, as Interface Builder doesn’t expose it directly). - Add a
UIScrollViewwith paging enabled, containing three pages.
Swift Implementation
import UIKit
import Lottie
class OnboardingViewController: UIViewController, UIScrollViewDelegate {
private let animationView = LottieAnimationView(name: "onboarding.json")
private let scrollView = UIScrollView()
private let pageCount = 3
override func viewDidLoad() {
super.viewDidLoad()
setupAnimation()
setupScrollView()
}
private func setupAnimation() {
animationView.contentMode = .scaleAspectFit
animationView.loopMode = .playOnce
animationView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(animationView)
NSLayoutConstraint.activate([
animationView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
animationView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
animationView.topAnchor.constraint(equalTo: view.topAnchor),
animationView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5)
])
}
private func setupScrollView() {
scrollView.isPagingEnabled = true
scrollView.delegate = self
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: animationView.bottomAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
// Add placeholder pages
for i in 0..
In this example the animation’s currentProgress is driven by the scroll offset, creating a seamless blend between user interaction and motion. The animation plays once per page transition, but you can also set loopMode to .loop for continuous motion.
Pro tip: When syncing scroll offset to animation progress, clamp the value between 0 and 1 to avoid jitter on fast swipes.
Example 3: Lottie in Flutter for a Success Toast
Flutter developers love the “toast” pattern for quick feedback. By pairing a short Lottie animation with a SnackBar, you can deliver a delightful success message.
pubspec.yaml
flutter:
assets:
- assets/success.json
Widget Code
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
class SuccessToast extends StatelessWidget {
final String message;
const SuccessToast({Key? key, required this.message}) : super(key: key);
@override
Widget build(BuildContext context) {
return SnackBar(
backgroundColor: Colors.black87,
content: Row(
children: [
// Lottie animation (auto‑plays once)
SizedBox(
width: 40,
height: 40,
child: Lottie.asset(
'assets/success.json',
repeat: false,
),
),
const SizedBox(width: 12),
Expanded(child: Text(message, style: const TextStyle(color: Colors.white))),
],
),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
);
}
}
// Usage inside any widget
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SuccessToast(message: 'Profile updated!'),
);
},
child: const Text('Save'),
);
The Lottie.asset widget loads the JSON from the bundled assets, plays it once, and then stops. Because the SnackBar automatically dismisses after two seconds, the animation feels tightly coupled with the feedback loop.
Pro tip: For frequent toast animations, pre‑cache the Lottie composition using LottieCache to avoid a frame‑drop on first display.
Advanced Techniques
Beyond basic playback, Lottie offers a suite of APIs for deeper control. Below are three advanced patterns you might encounter in production.
Dynamic Color Replacement
Sometimes brand colors change at runtime. Lottie lets you replace colors without editing the original JSON.
- Android:
addValueCallback(KeyPath("**.Fill 1.Color"), LottieValueCallback(Color.RED)) - iOS:
animationView.setValueProvider(LottieColorValueProvider(.red), keypath: AnimationKeypath(keypath: "**.Fill 1.Color")) - Flutter:
LottieBuilder.asset('anim.json', delegates: LottieDelegates(values: [ValueDelegate.color(['**', 'Fill 1', 'Color'], value: Colors.red)]))
Segmented Playback
When you need only a part of an animation—say, a “checkmark” at the end of a longer sequence—you can define start and end frames.
// Android example
val animationView = findViewById<LottieAnimationView>(R.id.anim)
animationView.setMinAndMaxFrame(30, 90) // play frames 30‑90 only
animationView.playAnimation()
Segmented playback reduces unnecessary processing and can be combined with user actions (e.g., play a success segment only after a form validates).
Performance Monitoring
Large animations can still cause frame drops on low‑end hardware. Lottie provides a built‑in setPerformanceTrackingEnabled(true) method (Android) that logs render times. Use this during QA to identify heavy vector shapes or excessive image assets.
Pro tip: If an animation consistently drops below 30 fps, consider simplifying shape layers or converting complex vector groups into raster images (PNG) inside After Effects, then re‑export.
Real‑World Use Cases
1. E‑commerce onboarding: A series of Lottie illustrations guide users through product discovery, with each swipe revealing a new animation that explains a feature.
2. Health‑tracking apps: Animated progress circles show daily step goals, updating in real time as the user walks. The vector nature ensures the circle looks sharp on every watch face.
3. Gaming UI: In‑game menus use Lottie for button hover effects and achievement pop‑ups, keeping the bundle size low while delivering a polished feel.
Testing and Debugging Lottie Animations
Even with thorough design reviews, animations can misbehave on specific devices. Here’s a quick checklist.
- Device Matrix: Test on at least one low‑end Android device (e.g., 720p, 2 GB RAM) and one high‑end iPhone to catch performance differences.
- Network Conditions: If you load JSON from a remote URL, simulate slow connections to ensure fallback UI appears.
- Memory Profiling: Use Android Studio’s Profiler or Xcode Instruments to watch GPU memory while the animation runs.
- Log Errors: Lottie logs parsing errors to Logcat/Console. Look for messages like “Unable to parse color” which indicate unsupported features.
Best Practices Checklist
- Keep JSON < 150 KB for most UI elements.
- Prefer shape layers; avoid raster images unless essential.
- Set
autoPlayandloopwisely to prevent unintended CPU cycles. - Use
setPerformanceTrackingEnabledduring QA. - Cache frequently used animations.
- Version‑lock the Lottie library to avoid breaking changes.
Conclusion
Lottie bridges the gap between design and development, allowing mobile teams to ship sophisticated vector animations with minimal effort. By mastering asset preparation, platform‑specific integration, and advanced runtime controls, you can create engaging experiences that run smoothly on any device. Remember to keep animations lightweight, test across a device spectrum, and leverage the library’s dynamic APIs for maximum flexibility. With these practices in place, your apps will not only look great—they’ll feel great too.