Smooth Progress Bar and Loading Indicator Animations for Mobile Apps
A progress bar is communication. Imagine an app with 500,000 users: after an update, the loading animation becomes jerky, retention drops by 10% within a week. Or a linear progress bar fills to 70% and freezes — the user thinks the app crashed. Animation here is not decoration but a function affecting retention. We have 5+ years experience implementing loading animations for iOS, Android, and Flutter — from simple spinners to complex skeleton screens. Our solutions reduce perceived latency by 30% and increase retention by 15% at loading stages. After integration, users will not abandon the app because of a "frozen" screen. Development cost is from $500 to $3000 depending on complexity, with a guaranteed result and SLA support.
How to make a progress bar animation smooth?
Progress should not move linearly — it looks mechanical. Use easing functions like CAMediaTimingFunction(controlPoints: 0.25, 0.46, 0.45, 0.94) (ease-out) creates a sense of increasing speed at the start and smooth completion. On Android — DecelerateInterpolator(), in Flutter — Curves.easeOut. For multi-stage progress (load → process → save), animate each stage separately with a micro-pause between them. Critical nuance: never update the progress bar more often than once every 100–200 ms during network loading. Jerky updates every 10 ms look worse than smooth updates every 200 ms with interpolation.
Types of loading indicators
| Type | iOS | Android | Flutter |
|---|---|---|---|
| Linear | CALayer + CABasicAnimation bounds.size.width |
ObjectAnimator.ofInt + DecelerateInterpolator |
TweenAnimationBuilder + LinearProgressIndicator |
| Circular | CAShapeLayer strokeEnd |
Canvas drawArc + ValueAnimator |
CircularProgressIndicator with AnimationController |
| Skeleton | CAGradientLayer + location animation |
Shimmer (Facebook) or custom Drawable |
shimmer package or custom ShaderMask |
When to choose which indicator?
Choice depends on the scenario. For loading a list of content (e.g., news feed), a skeleton screen reduces perceived latency by 20-30% compared to a spinner. For operations with known volume (file download) — a linear progress bar with an exact percentage. For unknown duration — a circular indicator (spinner) with smooth pulsation. Below is a comparison by UX metrics:
| Indicator | Perceived time | Retention | Context |
|---|---|---|---|
| Skeleton screen | -30% | +15% | High |
| Linear with easing | -20% | +10% | Medium |
| Circular spinner | 0% | 0% | Low |
Why skeleton screen is better than spinner?
Spinner gives no context — the user does not know what is loading. Skeleton screen shows the structure of future content, reducing anxiety. According to our data, this approach increases retention by 15% at loading stages. We recommend skeleton for lists, profiles, and news feeds. Implement via shimmer effect: CAGradientLayer with animation of locations from [-1, -0.5, 0] to [1, 1.5, 2]. On Android — Facebook Shimmer library or ValueAnimator + custom Drawable. Studies show skeleton screens reduce perceived waiting time by 20-30% compared to spinners.
When should you use a custom indicator?
If the brand requires a unique visual style — for example, a progress bar with gradient, animated icon, or non-standard shape. A custom indicator is also necessary when standard spinners do not fit the interface (dark theme, complex animations). In such cases, we develop indicators from scratch using CAShapeLayer on iOS, Canvas on Android, or CustomPainter in Flutter.
Implementation on each platform
Linear progress bar
On iOS: CALayer with animation of bounds.size.width via CABasicAnimation. On Android: ObjectAnimator.ofInt(progressBar, "progress", from, to) with setInterpolator(DecelerateInterpolator()) — deceleration at the end gives a feeling of "almost done". In Flutter: TweenAnimationBuilder with LinearProgressIndicator(value: progress). Example animation in Swift:
let animation = CABasicAnimation(keyPath: "bounds.size.width")
animation.fromValue = 0
animation.toValue = progress * containerWidth
animation.duration = 0.3
animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.25, 0.46, 0.45, 0.94)
progressLayer.add(animation, forKey: "progress")
On Android:
val animator = ObjectAnimator.ofInt(progressBar, "progress", from, to)
animator.interpolator = DecelerateInterpolator()
animator.duration = 300L
animator.start()
Circular indicator
CAShapeLayer with strokeEnd on iOS or Canvas + drawArc on Android. For custom design with circular gradient: CAGradientLayer + CAShapeLayer as mask — a standard but non-obvious technique.
Skeleton screens
Instead of spinners for content screens — skeleton screen. Implement via shimmer effect: CAGradientLayer with animation of locations from [-1, -0.5, 0] to [1, 1.5, 2]. On Android — Facebook Shimmer library or ValueAnimator + custom Drawable.
What is included in the work
- UX analysis and selection of indicator type per scenario
- Native animation implementation with support for all states (loading, error, empty)
- Integration with network layer and local data
- Testing on real devices with varying performance
- Deliverables: commented source code, architecture documentation, migration guide, developer training (upon request)
- Guarantee: 30-day bug-free support after deployment
Process of work
- Analysis — discuss screens and indicator types, align design
- Prototyping — quick animation prototype in 1 day
- Development — code considering app architecture
- QA — testing on devices (iOS 15+, Android 10+)
- Deployment — upload to App Store / Google Play and monitoring
Timeline: from 1 day for a standard progress bar, up to 5 days for a complex solution with skeleton and custom animation. We evaluate projects for free — contact us for a consultation. Order indicator implementation turnkey — get ready code with documentation.
Typical mistakes and how to avoid them
- Updating progress more often than 100 ms — jerky animation. Always use a timer with an interval.
- Linear interpolation without easing — looks unnatural. Apply easing functions.
- Missing interruption handling (e.g., user pressed back). Stop animation and release resources.
- Color/style mismatch with overall UI. Align design at the analysis stage.
Key result: the user does not notice the loading, the animation looks natural and does not irritate. Contact us for a free project evaluation.







