Limited offer — lifetime access:$99$79
Articlemobileiosandroidflutterrivelottieanimated icons

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:

RiveLottie
File sizeSmaller (binary .riv)Larger (text JSON)
InteractivityState machines — hover, tap, scrollLimited
iOS SDKrive-ios (Swift/ObjC)lottie-ios (AirBnB)
Android SDKrive-androidlottie-android (AirBnB)
Flutter packagerivelottie
After Effects workflowNoYes

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

swift
copy
import 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):

swift
copy
import 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 trigger
  • setBooleanInput("name", value: true) — boolean toggle
  • setNumberInput("name", value: 0.5) — numeric value

Lottie on iOS (Swift)

Install via Swift Package Manager:

https://github.com/airbnb/lottie-ios

swift
copy
import 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:

kotlin
copy
dependencies {
    implementation("app.rive:rive-android:9.6.2")
}

With Jetpack Compose:

kotlin
copy
import 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:

kotlin
copy
val 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:

kotlin
copy
dependencies {
    implementation("com.airbnb.android:lottie-compose:6.4.0")
}
kotlin
copy
import 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:

yaml
copy
dependencies:
  rive: ^0.13.0
dart
copy
import '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:

dart
copy
class 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

yaml
copy
dependencies:
  lottie: ^3.1.0
dart
copy
import '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.