Mobile Development 39 min read

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.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Avoid Animation Pitfalls: Comparing Multiple Android Animation Solutions and Choosing the Right One

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

NativePerformanceAnimationLottieAndroidSurfaceViewTextureViewPAG
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.