Avoid Animation Pitfalls: Comparing Multiple Android Animation Solutions and Choosing the Right One
This article dissects Android's rendering pipeline, quantitatively compares Native, Lottie, PAG, SurfaceView and TextureView animation implementations, and presents a step‑by‑step selection guide with practical code examples to help developers achieve high‑performance, low‑lag UI effects.
Introduction
With the rise of hardware capabilities, Android users demand smoother and more complex animations. The article starts from the Android rendering kernel, explains why many developers only know how to use animation APIs without understanding the underlying rendering mechanisms, leading to frame drops, OOM, and compatibility issues.
Android Rendering Kernel Basics
The rendering pipeline follows CPU → GPU → Screen . Key components include Canvas, VSYNC (60 Hz, 90 Hz, 120 Hz), RenderThread, hardware acceleration, and Choreographer, which binds animation callbacks to VSYNC.
Measurements are performed on a 60 Hz device. The article defines three performance metrics:
Resource loading time (from setAnimation/load to ready for first playback).
Per‑frame refresh time (fixed 16.67 ms for 60 Hz).
Actual draw time per frame (CPU + GPU processing).
Core Bottlenecks
CPU bottleneck: complex path calculations, frame decoding, object allocation.
GPU bottleneck: texture upload, layer compositing, vertex processing.
Link bottleneck: thread synchronization and resource loading.
Four Animation Solutions – Deep Dive
1. Native Animation
Native animations are divided into View animation, Frame animation, Property animation, Canvas custom drawing, and special animations (physics, transition, vector drawable).
Key difference between View animation and Property animation is whether the view's real properties change.
private var lastFrameTime: Long = 0
private val frameCallback = object : Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
if (lastFrameTime != 0L) {
val frameInterval = (frameTimeNanos - lastFrameTime) / 1_000_000f
Log.d(TAG, "[VSYNC interval] = $frameInterval ms")
}
lastFrameTime = frameTimeNanos
Choreographer.getInstance().postFrameCallback(this)
}
}Logs show a stable 16.67 ms interval, confirming correct VSYNC binding. Hardware acceleration does not affect VSYNC frequency but reduces draw time.
Benchmark results:
View animation modifies only the drawing matrix; real properties stay unchanged, causing post‑animation click‑area mismatch.
Property animation directly changes properties like translationX, eliminating extra matrix calculations and keeping the view in its final state.
2. Lottie Animation
Lottie uses JSON‑driven vector rendering, supporting cross‑platform playback.
Key steps:
Parse JSON to build LottieComposition (layers, keyframes, frameRate, bounds, images, fonts).
Construct a layer tree (ShapeLayer, ImageLayer, TextLayer).
Render each frame via Canvas or OpenGL.
Off‑screen rendering is used for masks and blend modes, which adds buffer copy overhead.
Performance data:
JSON parsing takes ~520 ms (500 ms on a 800 KB file).
Single‑frame render time varies between 11 ms and 21 ms; some frames exceed the 16.67 ms budget, causing occasional frame drops.
3. PAG Animation
PAG is a Tencent‑developed binary animation format (ProtoBuf). It reduces file size by >50 % and parsing time to ~5 ms for an 800 KB file.
public class LottieComposition {
public final Map<Long, Layer> layers;
public final List<Keyframe<?>> keyframes;
public final float frameRate;
public final float startFrame;
public final float endFrame;
public final Size bounds;
public final Map<String, LottieImageAsset> images;
public final Map<String, LottieFont> fonts;
}PAG rendering runs on a dedicated thread (tgfx_JNIEnvironment) that parses the current frame, interpolates properties, builds draw commands, and submits them to the GPU. Average frame time is 14.7 ms, ~14 % faster than Lottie (17.1 ms).
4. SurfaceView Animation
SurfaceView separates the drawing surface from the view hierarchy. Rendering occurs on a separate thread using lockCanvas() / unlockCanvasAndPost(). The surface buffer queue (2‑3 buffers) is the main bottleneck; if drawing exceeds the refresh interval, lockCanvas() blocks, causing frame stalls and possible black screens.
2026-02-06-09 51:16.62515462-20382 AnimationS... drawFrame threadName = Thread[Thread-1036,5,main]
2026-02-06-09 51:16.64015462-20383 AnimationS... drawFrame threadName = Thread[Thread-1037,5,main]Typical draw time is ~1.7 ms, well within the budget, but buffer contention can cause spikes on low‑end devices.
5. TextureView Animation
TextureView aligns its lifecycle with the view hierarchy and uses a SurfaceTexture that can be kept across configuration changes. Rendering runs on a custom thread, allowing continuous background animation without blocking the UI.
class ApertureTextureView extends TextureView implements SurfaceTextureListener {
private Paint circlePaint;
private List<Circle> circleList;
private boolean isAnimRunning = false;
private RenderThread renderThread;
// Surface creation
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
initCircleData(width, height);
startRenderThread();
}
// Rendering loop
private class RenderThread extends Thread {
@Override
public void run() {
long last = System.currentTimeMillis();
while (isAnimRunning) {
long now = System.currentTimeMillis();
long interval = Math.min(now - last, 50);
last = now;
updateCirclePosition(interval);
Canvas canvas = lockCanvas();
if (canvas != null) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
drawAllCircles(canvas);
unlockCanvasAndPost(canvas);
}
long cost = System.currentTimeMillis() - now;
if (cost < 16) Thread.sleep(16 - cost);
}
}
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
isAnimRunning = false;
renderThread.join(1000);
return true;
}
}Log shows per‑frame total cost of 8‑10 ms, comfortably below the 16.67 ms threshold.
Practical Selection Process
The article proposes a four‑step filtering method:
Exclude solutions that cannot meet dynamic color requirements (Lottie, PAG).
Exclude approaches that would block the UI for long‑running background animations (Native Canvas).
Prefer lightweight solutions for simple background effects (TextureView over SurfaceView).
Final recommendation for the “three‑color circular background” use‑case is TextureView, which satisfies color customisation, runs on a background thread, and stays within the performance budget.
Decision Guidelines
Selection should prioritize:
Performance compliance: frame time ≤ screen refresh interval.
Scenario fit: feature set matches product needs.
Development cost: use familiar stacks.
Compatibility & stability: support target Android versions and avoid lifecycle pitfalls.
For complex vector animations, choose PAG (best performance) or Lottie (cross‑platform). For simple, high‑frequency background effects, use TextureView. Verify each candidate with quantitative benchmarks as demonstrated.
Conclusion
Animation selection is a balance of scenario matching and performance vs. cost. Native animations cover most simple cases, PAG excels in performance for heavy vector assets, Lottie offers cross‑platform convenience, SurfaceView provides raw performance for extreme cases, and TextureView gives a safe, low‑overhead solution for continuous background animations. All solutions must be validated against the core metric of ≤ 16.67 ms per frame to avoid jank.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
