Animated Icons for Mobile Apps — iOS, Android & Flutter Guide
How to use animated icons in mobile apps with Rive and Lottie. Covers iOS (Swift), Android (Jetpack Compose), and Flutter with working code examples and performance tips.
Animated icons make mobile UIs feel polished and responsive — a loading spinner, a checkmark that draws itself, a menu icon that morphs to a close button. This guide covers integrating animated icons on iOS, Android, and Flutter using Rive and Lottie.
Get the icon files at Unicorn Icons — every icon ships as a native .riv and .json file, optimized for mobile runtimes.
Rive vs Lottie on mobile
Both formats have mature mobile SDKs, but they have different trade-offs:
| Rive | Lottie | |
|---|---|---|
| File size | Smaller (binary .riv) | Larger (text JSON) |
| Interactivity | State machines — hover, tap, scroll | Limited |
| iOS SDK | rive-ios (Swift/ObjC) | lottie-ios (AirBnB) |
| Android SDK | rive-android | lottie-android (AirBnB) |
| Flutter package | rive | lottie |
| After Effects workflow | No | Yes |
For interactive icons that respond to user input (tap, swipe, loading state), use Rive. For decorative looping animations, either works.
All Unicorn Icons ship both formats — pick what fits your project.
iOS
Rive on iOS (Swift)
Install via Swift Package Manager:
In Xcode: File → Add Package → https://github.com/rive-app/rive-ios
swiftcopyimport RiveRuntime import SwiftUI struct RiveIcon: View { var body: some View { RiveViewModel(fileName: "arrow").view() .frame(width: 32, height: 32) } }
With a state machine (hover/tap):
swiftcopyimport RiveRuntime import SwiftUI struct InteractiveIcon: View { @StateObject private var viewModel = RiveViewModel( fileName: "arrow", stateMachineName: "State Machine 1" ) var body: some View { viewModel.view() .frame(width: 32, height: 32) .onTapGesture { viewModel.triggerInput("Click") } } }
State machine input types:
triggerInput("name")— one-shot triggersetBooleanInput("name", value: true)— boolean togglesetNumberInput("name", value: 0.5)— numeric value
Lottie on iOS (Swift)
Install via Swift Package Manager:
https://github.com/airbnb/lottie-ios
swiftcopyimport Lottie import SwiftUI struct LottieIcon: UIViewRepresentable { let name: String var loop: LottieLoopMode = .loop func makeUIView(context: Context) -> LottieAnimationView { let view = LottieAnimationView(name: name) view.loopMode = loop view.play() view.contentMode = .scaleAspectFit return view } func updateUIView(_ uiView: LottieAnimationView, context: Context) {} } // Usage LottieIcon(name: "arrow") .frame(width: 32, height: 32)
Full platform guide: Lottie on iOS · Rive on iOS
Android
Rive on Android (Kotlin/Compose)
Add to build.gradle:
kotlincopydependencies { implementation("app.rive:rive-android:9.6.2") }
With Jetpack Compose:
kotlincopyimport androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView import app.rive.runtime.kotlin.RiveAnimationView @Composable fun RiveIcon( resId: Int, stateMachineName: String = "State Machine 1", modifier: Modifier = Modifier ) { AndroidView( factory = { context -> RiveAnimationView(context).apply { setRiveResource(resId, stateMachineName = stateMachineName) } }, modifier = modifier ) }
Trigger a state machine input on tap:
kotlincopyval riveView = RiveAnimationView(context) riveView.setRiveResource(R.raw.arrow, stateMachineName = "State Machine 1") riveView.setOnClickListener { riveView.fireState("State Machine 1", "Click") }
Lottie on Android (Kotlin/Compose)
Add to build.gradle:
kotlincopydependencies { implementation("com.airbnb.android:lottie-compose:6.4.0") }
kotlincopyimport com.airbnb.lottie.compose.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @Composable fun LottieIcon(resId: Int, modifier: Modifier = Modifier) { val composition by rememberLottieComposition( LottieCompositionSpec.RawRes(resId) ) val progress by animateLottieCompositionAsState( composition, iterations = LottieConstants.IterateForever ) LottieAnimation( composition = composition, progress = { progress }, modifier = modifier ) }
Full platform guide: Lottie on Android · Rive on Android
Flutter
Rive on Flutter
Add to pubspec.yaml:
yamlcopydependencies: rive: ^0.13.0
dartcopyimport 'package:rive/rive.dart'; import 'package:flutter/material.dart'; class RiveIcon extends StatelessWidget { const RiveIcon({super.key}); @override Widget build(BuildContext context) { return const SizedBox( width: 32, height: 32, child: RiveAnimation.asset( 'assets/icons/arrow.riv', stateMachines: ['State Machine 1'], fit: BoxFit.contain, ), ); } }
Control state machine inputs:
dartcopyclass InteractiveRiveIcon extends StatefulWidget { const InteractiveRiveIcon({super.key}); @override State<InteractiveRiveIcon> createState() => _InteractiveRiveIconState(); } class _InteractiveRiveIconState extends State<InteractiveRiveIcon> { SMIBool? _hoverInput; void _onRiveInit(Artboard artboard) { final controller = StateMachineController.fromArtboard( artboard, 'State Machine 1', ); if (controller != null) { artboard.addController(controller); _hoverInput = controller.findInput<bool>('Hover') as SMIBool?; } } @override Widget build(BuildContext context) { return MouseRegion( onEnter: (_) => _hoverInput?.change(true), onExit: (_) => _hoverInput?.change(false), child: SizedBox( width: 32, height: 32, child: RiveAnimation.asset( 'assets/icons/arrow.riv', onInit: _onRiveInit, ), ), ); } }
Lottie on Flutter
yamlcopydependencies: lottie: ^3.1.0
dartcopyimport 'package:lottie/lottie.dart'; import 'package:flutter/material.dart'; class LottieIcon extends StatelessWidget { const LottieIcon({super.key}); @override Widget build(BuildContext context) { return Lottie.asset( 'assets/icons/arrow.json', width: 32, height: 32, fit: BoxFit.contain, ); } }
Full platform guide: Lottie on Flutter · Rive on Flutter
Performance tips for mobile
1. Keep icon files small. Rive .riv files are typically 5–20KB. Lottie JSON files are 20–80KB. Unicorn Icons files are optimized for mobile — no bloated layer data.
2. Pause off-screen animations. On Android, call pause() when the view scrolls off screen. On iOS, set isPlaying = false in viewDidDisappear. On Flutter, use AnimationController.stop().
3. Avoid rendering many animations simultaneously. Each Rive/Lottie instance is a canvas element. If you have a list of 50 icons, only animate the visible ones.
4. Use vector formats. Both Rive and Lottie render as vectors — they scale perfectly at any screen density without larger asset variants.
Browse mobile-ready icons
Browse icons for iOS → Browse icons for Android → Browse icons for Flutter →
All icons are free to preview. Download SVG/PNG free, or upgrade once for Lottie and Rive formats.