Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Lottie animation skill for integrating After Effects animations into iOS, Android, and cross-platform mobile apps with playback control, performance optimization, and interactive animation capabilities.
.claude/skills/a5c-ai-lottie-animations/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 195% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 170% | 0% |
| case-21 | ✗→✓ | ▲ Improved | 884% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 231% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 169% | 0% |
Comprehensive Lottie animation integration for mobile platforms, enabling high-quality vector animations from After Effects in iOS, Android, React Native, and Flutter applications.
This skill provides capabilities for integrating Lottie animations into mobile applications, including animation playback control, performance optimization, interactive animations, and proper asset management across platforms.
ruby# Podfile pod 'lottie-ios' # Or Swift Package Manager # https://github.com/airbnb/lottie-ios.git
groovy// build.gradle dependencies { implementation 'com.airbnb.android:lottie:6.3.0' }
bashnpm install lottie-react-native # or yarn add lottie-react-native # iOS pods cd ios && pod install
yaml# pubspec.yaml dependencies: lottie: ^3.0.0
swiftimport SwiftUI import Lottie struct LottieView: UIViewRepresentable { let animationName: String let loopMode: LottieLoopMode let animationSpeed: CGFloat @Binding var isPlaying: Bool func makeUIView(context: Context) -> LottieAnimationView { let animationView = LottieAnimationView(name: animationName) animationView.contentMode = .scaleAspectFit animationView.loopMode = loopMode animationView.animationSpeed = animationSpeed return animationView } func updateUIView(_ animationView: LottieAnimationView, context: Context) { if isPlaying { animationView.play() } else { animationView.pause() } } } // Usage struct ContentView: View { @State private var isPlaying = true var body: some View { VStack { LottieView( animationName: "loading", loopMode: .loop, animationSpeed: 1.0, isPlaying: $isPlaying ) .frame(width: 200, height: 200) Button(isPlaying ? "Pause" : "Play") { isPlaying.toggle() } } } }
swiftimport Lottie class AnimationViewController: UIViewController { private var animationView: LottieAnimationView! override func viewDidLoad() { super.viewDidLoad() setupAnimation() } private func setupAnimation() { animationView = LottieAnimationView(name: "success") animationView.frame = CGRect(x: 0, y: 0, width: 200, height: 200) animationView.center = view.center animationView.contentMode = .scaleAspectFit animationView.loopMode = .playOnce view.addSubview(animationView) } func playAnimation() { animationView.play { completed in if completed { print("Animation completed") } } } func playSegment(from: CGFloat, to: CGFloat) { animationView.play(fromProgress: from, toProgress: to, loopMode: .playOnce) } func setProgress(_ progress: CGFloat) { animationView.currentProgress = progress } }
kotlinimport com.airbnb.lottie.compose.* @Composable fun LottieAnimationScreen() { val composition by rememberLottieComposition( LottieCompositionSpec.RawRes(R.raw.loading) ) val progress by animateLottieCompositionAsState( composition = composition, iterations = LottieConstants.IterateForever ) LottieAnimation( composition = composition, progress = { progress }, modifier = Modifier.size(200.dp) ) } // Controllable animation @Composable fun ControllableLottieAnimation() { val composition by rememberLottieComposition( LottieCompositionSpec.RawRes(R.raw.success) ) var isPlaying by remember { mutableStateOf(false) } val progress by animateLottieCompositionAsState( composition = composition, isPlaying = isPlaying, restartOnPlay = true ) Column(horizontalAlignment = Alignment.CenterHorizontally) { LottieAnimation( composition = composition, progress = { progress }, modifier = Modifier.size(200.dp) ) Button(onClick = { isPlaying = true }) { Text("Play") } } } // Progress-based animation @Composable fun ScrollSyncedAnimation(scrollProgress: Float) { val composition by rememberLottieComposition( LottieCompositionSpec.RawRes(R.raw.scroll_animation) ) LottieAnimation( composition = composition, progress = { scrollProgress }, modifier = Modifier.fillMaxWidth() ) }
kotlinimport com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieDrawable class AnimationActivity : AppCompatActivity() { private lateinit var animationView: LottieAnimationView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_animation) animationView = findViewById(R.id.animation_view) setupAnimation() } private fun setupAnimation() { animationView.apply { setAnimation(R.raw.loading) repeatCount = LottieDrawable.INFINITE speed = 1.0f } } fun playAnimation() { animationView.playAnimation() } fun pauseAnimation() { animationView.pauseAnimation() } fun setProgress(progress: Float) { animationView.progress = progress } fun playSegment(startFrame: Int, endFrame: Int) { animationView.setMinAndMaxFrame(startFrame, endFrame) animationView.playAnimation() } }
javascriptimport React, { useRef, useState } from 'react'; import { View, Button, StyleSheet } from 'react-native'; import LottieView from 'lottie-react-native'; const AnimationScreen = () => { const animationRef = useRef(null); const [isPlaying, setIsPlaying] = useState(false); const playAnimation = () => { animationRef.current?.play(); setIsPlaying(true); }; const pauseAnimation = () => { animationRef.current?.pause(); setIsPlaying(false); }; const resetAnimation = () => { animationRef.current?.reset(); setIsPlaying(false); }; return ( <View style={styles.container}> <LottieView ref={animationRef} source={require('./animations/success.json')} style={styles.animation} autoPlay={false} loop={false} onAnimationFinish={() => setIsPlaying(false)} /> <View style={styles.controls}> <Button title={isPlaying ? 'Pause' : 'Play'} onPress={isPlaying ? pauseAnimation : playAnimation} /> <Button title="Reset" onPress={resetAnimation} /> </View> </View> ); }; // Progress-controlled animation const ScrollAnimation = ({ scrollProgress }) => { return ( <LottieView source={require('./animations/scroll-animation.json')} progress={scrollProgress} style={styles.animation} /> ); }; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, animation: { width: 200, height: 200, }, controls: { flexDirection: 'row', marginTop: 20, }, }); export default AnimationScreen;
dartimport 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; class AnimationScreen extends StatefulWidget { @override _AnimationScreenState createState() => _AnimationScreenState(); } class _AnimationScreenState extends State<AnimationScreen> with SingleTickerProviderStateMixin { late final AnimationController _controller; @override void initState() { super.initState(); _controller = AnimationController(vsync: this); } @override void dispose() { _controller.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Lottie.asset( 'assets/animations/success.json', controller: _controller, width: 200, height: 200, onLoaded: (composition) { _controller.duration = composition.duration; }, ), SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () => _controller.forward(), child: Text('Play'), ), SizedBox(width: 10), ElevatedButton( onPressed: () => _controller.stop(), child: Text('Stop'), ), SizedBox(width: 10), ElevatedButton( onPressed: () => _controller.reset(), child: Text('Reset'), ), ], ), ], ), ), ); } } // Looping animation class LoadingAnimation extends StatelessWidget { @override Widget build(BuildContext context) { return Lottie.asset( 'assets/animations/loading.json', width: 100, height: 100, repeat: true, ); } } // Network animation class NetworkAnimation extends StatelessWidget { final String url; const NetworkAnimation({required this.url}); @override Widget build(BuildContext context) { return Lottie.network( url, width: 200, height: 200, frameRate: FrameRate.max, ); } }
javascriptconst lottieIntegrationTask = defineTask({ name: 'lottie-animation-setup', description: 'Integrate Lottie animations into mobile app', inputs: { platform: { type: 'string', required: true, enum: ['ios', 'android', 'react-native', 'flutter'] }, projectPath: { type: 'string', required: true }, animationFiles: { type: 'array', items: { type: 'string' } }, features: { type: 'array', items: { type: 'string', enum: ['playback_control', 'progress_sync', 'gestures', 'theming'] } } }, outputs: { integratedAnimations: { type: 'array' }, componentCode: { type: 'string' }, optimizationReport: { type: 'object' } }, async run(inputs, taskCtx) { return { kind: 'skill', title: `Integrate Lottie for ${inputs.platform}`, skill: { name: 'lottie-animations', context: { operation: 'integrate', platform: inputs.platform, projectPath: inputs.projectPath, animations: inputs.animationFiles, features: inputs.features } }, io: { inputJsonPath: `tasks/${taskCtx.effectId}/input.json`, outputJsonPath: `tasks/${taskCtx.effectId}/result.json` } }; } });
bash# Use LottieFiles optimizer npx @nicolo-ribaudo/lottie-optimize animation.json -o optimized.json # Or use online tools: # https://lottiefiles.com/tools/lottie-optimizer
swift// iOS - Clear cache when needed LottieAnimationView.clearCache() // Load from cache let animation = LottieAnimation.named("loading", animationCache: LRUAnimationCache.sharedCache)
kotlin// Android - Configure cache val cacheComposition = LottieCompositionFactory .fromRawRes(context, R.raw.animation) .addListener { composition -> // Animation loaded }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 35,803 | 41,983 | +17% | 1 | 1 | 0% | 6,240 | 8,950 | +43% | 0 | 0 | — |
case-02 | pass→pass | 8,859 | 11,311 | +28% | 1 | 1 | 0% | 1,789 | 5,928 | +231% | 0 | 0 | — |
case-03 | pass→pass | 17,517 | 18,361 | +5% | 1 | 1 | 0% | 2,601 | 7,006 | +169% | 0 | 0 | — |
case-04 | pass→pass | 13,970 | 13,407 | -4% | 1 | 1 | 0% | 2,204 | 6,216 | +182% | 0 | 0 | — |
case-05 | pass→pass | 11,142 | 10,581 | -5% | 1 | 1 | 0% | 2,223 | 5,748 | +159% | 0 | 0 | — |
case-06 | pass→pass | 5,719 | 5,227 | -9% | 1 | 1 | 0% | 1,123 | 4,631 | +312% | 0 | 0 | — |
case-07 | fail→pass | 8,718 | 5,738 | -34% | 1 | 1 | 0% | 1,612 | 4,760 | +195% | 0 | 0 | — |
case-08 | pass→pass | 5,580 | 4,115 | -26% | 1 | 1 | 0% | 1,126 | 4,438 | +294% | 0 | 0 | — |
case-09 | pass→pass | 9,757 | 8,731 | -11% | 1 | 1 | 0% | 1,744 | 5,291 | +203% | 0 | 0 | — |
case-10 | pass→pass | 4,120 | 4,155 | +1% | 1 | 1 | 0% | 754 | 4,370 | +480% | 0 | 0 | — |
case-11 | pass→pass | 5,734 | 4,927 | -14% | 1 | 1 | 0% | 1,011 | 4,533 | +348% | 0 | 0 | — |
case-12 | pass→pass | 8,757 | 9,109 | +4% | 1 | 1 | 0% | 1,787 | 5,559 | +211% | 0 | 0 | — |
case-13 | pass→pass | 9,547 | 6,868 | -28% | 1 | 1 | 0% | 1,386 | 4,943 | +257% | 0 | 0 | — |
case-14 | pass→pass | 15,044 | 11,642 | -23% | 1 | 1 | 0% | 3,204 | 5,503 | +72% | 0 | 0 | — |
case-15 | pass→pass | 3,850 | 3,217 | -16% | 1 | 1 | 0% | 689 | 4,216 | +512% | 0 | 0 | — |
case-16 | fail→pass | 10,349 | 8,080 | -22% | 1 | 1 | 0% | 1,810 | 4,886 | +170% | 0 | 0 | — |
case-17 | fail→fail | 8,212 | 6,166 | -25% | 1 | 1 | 0% | 1,679 | 4,713 | +181% | 0 | 0 | — |
case-18 | pass→pass | 6,814 | 6,267 | -8% | 1 | 1 | 0% | 1,181 | 4,716 | +299% | 0 | 0 | — |
case-19 | pass→pass | 5,838 | 2,124 | -64% | 1 | 1 | 0% | 884 | 3,924 | +344% | 0 | 0 | — |
case-20 | pass→pass | 6,870 | 2,110 | -69% | 1 | 1 | 0% | 1,238 | 3,945 | +219% | 0 | 0 | — |
case-21 | fail→pass | 2,293 | 2,033 | -11% | 1 | 1 | 0% | 398 | 3,917 | +884% | 0 | 0 | — |
case-22 | pass→pass | 2,782 | 1,796 | -35% | 1 | 1 | 0% | 481 | 3,741 | +678% | 0 | 0 | — |
case-23 | fail→fail | 11,533 | 8,084 | -30% | 1 | 1 | 0% | 1,826 | 4,650 | +155% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.