Chapter 14: Animation System¶
Android's animation system has evolved across four generations of APIs, each addressing a wider class of motion -- from simple view-level transforms through physics-based spring models to coordinated window-manager shell transitions. This chapter traces the full path an animated value takes from application code to the compositor, examines every major subsystem in detail, and shows how the pieces connect through Choreographer's VSYNC-driven timing pulse.
14.1 Animation Architecture Overview¶
14.1.1 Four Generations of Animation¶
Android provides four distinct animation layers, each built atop the one before it:
| Generation | API Level | Package / Location | Scope |
|---|---|---|---|
| View Animation (legacy) | 1 | android.view.animation |
Matrix + alpha on a single View |
| Property Animation | 11 | android.animation |
Arbitrary typed property on any Object |
| Transition Framework | 19 | android.transition |
Scene-change choreography across a ViewGroup |
| Shell Transitions | 12L+ | com.android.wm.shell.transition |
Cross-window, cross-task WM transitions |
Additionally, the platform provides specialized subsystems for physics-based
motion (DynamicAnimation, SpringAnimation, FlingAnimation), native
RenderThread animations (HWUI), and drawable-level animations
(AnimatedVectorDrawable).
14.1.2 End-to-End Animation Data Flow¶
graph TD
subgraph "Application Process"
A[App Code: animator.start] --> B[AnimationHandler]
B --> C[Choreographer CALLBACK_ANIMATION]
C --> D[ValueAnimator.doAnimationFrame]
D --> E[PropertyValuesHolder.setAnimatedValue]
E --> F[View.setTranslationX / setAlpha / ...]
F --> G[RenderNode property update]
end
subgraph "RenderThread"
G --> H[HWUI AnimatorManager.pushStaging]
H --> I[BaseRenderNodeAnimator.animate]
I --> J[RenderNode draw ops]
J --> K[SurfaceFlinger composition]
end
subgraph "System Server WM"
L[Transition request] --> M[TransitionController]
M --> N[SurfaceAnimator creates leash]
N --> O[SurfaceAnimationRunner]
O --> P[ValueAnimator on AnimationThread]
P --> Q[SurfaceControl.Transaction]
Q --> K
end
subgraph "Shell Process"
R[Transitions.java onTransitionReady] --> S[TransitionHandler.startAnimation]
S --> T[DefaultTransitionHandler]
T --> U[Animation on SurfaceControl]
U --> K
end
14.1.3 Timing Infrastructure¶
All animations on the UI thread share a single timing source: the Choreographer. Choreographer receives VSYNC signals from the display subsystem and dispatches five ordered callback types every frame:
// frameworks/base/core/java/android/view/Choreographer.java, lines 311-353
CALLBACK_INPUT = 0 // Input events
CALLBACK_ANIMATION = 1 // Animator frame callbacks
CALLBACK_INSETS_ANIMATION = 2 // WindowInsetsAnimation updates
CALLBACK_TRAVERSAL = 3 // View measure/layout/draw
CALLBACK_COMMIT = 4 // Post-draw commit; adjusts start time for skipped frames
The AnimationHandler registers a FrameCallback with Choreographer that,
on each VSYNC, iterates all registered AnimationFrameCallback instances --
which includes every running ValueAnimator and DynamicAnimation.
sequenceDiagram
participant VSYNC as Display VSYNC
participant Choreo as Choreographer
participant AH as AnimationHandler
participant VA as ValueAnimator
participant Obj as Target Object
participant RT as RenderThread
VSYNC->>Choreo: VSYNC signal
Choreo->>AH: doFrame(frameTimeNanos)
AH->>AH: doAnimationFrame(frameTime)
AH->>VA: doAnimationFrame(frameTime)
VA->>VA: animateValue(fraction)
VA->>Obj: setValue(interpolated)
Obj->>RT: invalidate / property push
RT->>RT: draw frame
14.1.4 Key Source Directories¶
| Directory | Contents | Lines (approx) |
|---|---|---|
frameworks/base/core/java/android/view/animation/ |
View Animation classes | ~5,800 |
frameworks/base/core/java/android/animation/ |
Property Animation framework | ~13,400 |
frameworks/base/core/java/android/transition/ |
Transition Framework | ~9,200 |
frameworks/base/libs/hwui/ (Animator*) |
Native HWUI animators | ~830 |
frameworks/base/core/java/android/view/Choreographer.java |
Timing pulse | 1,714 |
frameworks/base/services/core/java/com/android/server/wm/ (anim) |
WM animation infrastructure | ~2,400 |
frameworks/base/libs/WindowManager/Shell/src/.../transition/ |
Shell transitions | ~8,200 |
frameworks/base/libs/WindowManager/Shell/src/.../back/ |
Predictive back | ~3,200 |
frameworks/base/core/java/com/android/internal/dynamicanimation/animation/ |
Physics animations | ~1,750 |
14.1.5 Thread Model¶
Understanding which thread runs each animation type is critical for performance analysis:
graph TD
subgraph "UI Thread (Main Looper)"
VA[ValueAnimator]
OA[ObjectAnimator]
AS[AnimatorSet]
SA[SpringAnimation]
FA[FlingAnimation]
LT[LayoutTransition]
TF[Transition Framework]
end
subgraph "RenderThread"
HWUI[HWUI BaseRenderNodeAnimator]
AVD[AnimatedVectorDrawable native]
VPA[ViewPropertyAnimator native path]
end
subgraph "AnimationThread (system_server)"
SAR[SurfaceAnimationRunner]
end
subgraph "SurfaceAnimationThread (system_server)"
SATH[Surface animation handler]
end
subgraph "Shell Main Thread"
ST[Shell Transitions]
DTH[DefaultTransitionHandler]
BAC[BackAnimationController]
end
The key insight is that ViewPropertyAnimator and AnimatedVectorDrawable (API 25+) run natively on the RenderThread, making them immune to UI thread jank. All other Java-based animations run on the UI thread and are susceptible to interruption by garbage collection, heavy layout, or other main-thread work.
14.1.6 Animation Coordination Across Processes¶
Modern Android animations often span multiple processes:
sequenceDiagram
participant App as App Process
participant SS as System Server (WM)
participant Shell as Shell Process
participant SF as SurfaceFlinger
Note over App: User taps launcher icon
App->>SS: startActivity()
SS->>SS: Create Transition, collect windows
SS->>SS: Wait for window draws
SS->>Shell: onTransitionReady(TransitionInfo)
Shell->>Shell: DefaultTransitionHandler.startAnimation()
loop each frame
Shell->>SF: SurfaceControl.Transaction
SF->>SF: Compose and present
end
Shell->>SS: finishTransition()
SS->>App: Activity fully visible
The animation runs in the Shell process, but it affects surfaces from the App process. This decoupling means app jank does not affect system transition animations.
14.1.7 Animation Duration and Scale¶
All animations in Android are subject to the global animation scale settings. There are three independent scale factors:
| Setting | Affects | Default |
|---|---|---|
animator_duration_scale |
Property animations (ValueAnimator, etc.) | 1.0 |
window_animation_scale |
Window open/close animations | 1.0 |
transition_animation_scale |
Activity transitions | 1.0 |
These can be modified through Developer Options or programmatically:
adb shell settings put global animator_duration_scale 2.0
adb shell settings put global window_animation_scale 0.5
adb shell settings put global transition_animation_scale 0
When any scale is set to 0, the corresponding animations are disabled (complete instantly).
14.1.8 Frame Budget¶
At 60Hz, each frame has a budget of 16.67ms. At 120Hz, the budget is 8.33ms. The animation callback must complete within a fraction of this budget to allow time for layout, draw, and GPU work:
| Phase | Budget (60Hz) | Budget (120Hz) |
|---|---|---|
| INPUT callbacks | ~1ms | ~0.5ms |
| ANIMATION callbacks | ~2ms | ~1ms |
| INSETS_ANIMATION | ~0.5ms | ~0.3ms |
| TRAVERSAL (layout + draw) | ~10ms | ~5ms |
| GPU render | ~3ms | ~1.5ms |
| Total | ~16.5ms | ~8.3ms |
Exceeding the budget causes frame drops (jank). Choreographer logs a warning when more than 30 frames are skipped.
14.2 View Animation (Legacy)¶
14.2.1 Overview¶
The original animation framework, present since API 1, operates by applying a
Transformation (matrix + alpha) to a View during the drawing phase.
Crucially, View Animations do not change the actual layout properties of
the view -- a translated view still receives touch events at its original
position.
Source directory:
frameworks/base/core/java/android/view/animation/ (29 files)
14.2.2 The Animation Base Class¶
The abstract class Animation (1,363 lines) defines the lifecycle:
// frameworks/base/core/java/android/view/animation/Animation.java, lines 40-98
public abstract class Animation implements Cloneable {
public static final int INFINITE = -1;
public static final int RESTART = 1;
public static final int REVERSE = 2;
public static final int START_ON_FIRST_FRAME = -1;
public static final int ABSOLUTE = 0;
public static final int RELATIVE_TO_SELF = 1;
public static final int RELATIVE_TO_PARENT = 2;
public static final int ZORDER_NORMAL = 0;
public static final int ZORDER_TOP = 1;
public static final int ZORDER_BOTTOM = -1;
...
}
Key internal state (lines 110-237):
| Field | Type | Purpose |
|---|---|---|
mEnded |
boolean | Set by getTransformation() when animation ends |
mStarted |
boolean | Set on first frame |
mCycleFlip |
boolean | Toggles in REVERSE repeat mode |
mInitialized |
boolean | Must be true before playing |
mFillBefore |
boolean | Apply transform before start (default true) |
mFillAfter |
boolean | Persist transform after end |
mStartTime |
long | Absolute start time in millis |
mDuration |
long | Duration of one cycle |
mRepeatCount |
int | Number of repeats (0 = play once) |
mRepeatMode |
int | RESTART or REVERSE |
mInterpolator |
Interpolator | Easing curve |
mScaleFactor |
float | Scale for pivot points |
The lifecycle is driven by getTransformation(long, Transformation), which
computes elapsed time, applies the interpolator, and calls the abstract method
applyTransformation(float interpolatedTime, Transformation t).
stateDiagram-v2
[*] --> NotStarted
NotStarted --> Initialized: initialize w h pw ph
Initialized --> Running: getTransformation first call
Running --> Running: getTransformation each frame
Running --> Repeating: repeat count not exhausted
Repeating --> Running: next cycle
Running --> Ended: duration exhausted
Ended --> [*]
Running --> Cancelled: cancel
Cancelled --> [*]
14.2.3 Transformation¶
The Transformation class (lines 32-80) encapsulates what a View Animation
produces:
// frameworks/base/core/java/android/view/animation/Transformation.java, lines 32-48
public class Transformation {
public static final int TYPE_IDENTITY = 0x0;
public static final int TYPE_ALPHA = 0x1;
public static final int TYPE_MATRIX = 0x2;
public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
protected Matrix mMatrix;
protected float mAlpha;
protected int mTransformationType;
...
}
A Transformation holds a Matrix (for translate/rotate/scale) and an
alpha value. The compose(Transformation) method concatenates two
transformations, which is how AnimationSet combines child animations.
14.2.4 Concrete Animation Subclasses¶
AlphaAnimation¶
The simplest animation -- modifies only the alpha component:
// frameworks/base/core/java/android/view/animation/AlphaAnimation.java, lines 67-70
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
Note willChangeTransformationMatrix() returns false (line 73) -- no matrix
modification, just alpha blending.
TranslateAnimation¶
Moves a view by modifying the matrix translation:
// frameworks/base/core/java/android/view/animation/TranslateAnimation.java, lines 166-176
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}
The initialize() method (line 179) resolves value types:
ABSOLUTE-- pixel values used directlyRELATIVE_TO_SELF-- multiplied by the view's own dimensionsRELATIVE_TO_PARENT-- multiplied by the parent's dimensions
RotateAnimation¶
Rotates around a configurable pivot point:
// frameworks/base/core/java/android/view/animation/RotateAnimation.java, lines 166-175
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
ScaleAnimation¶
Scales with pivot support; resolves from/to values which may be fractions or dimensions:
// frameworks/base/core/java/android/view/animation/ScaleAnimation.java, lines 241-258
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
14.2.5 AnimationSet¶
AnimationSet (553 lines) groups multiple animations that play together.
Its getTransformation() iterates children in reverse order and calls
compose() to concatenate their transformations:
// frameworks/base/core/java/android/view/animation/AnimationSet.java, lines 390-423
@Override
public boolean getTransformation(long currentTime, Transformation t) {
final int count = mAnimations.size();
final ArrayList<Animation> animations = mAnimations;
final Transformation temp = mTempTransformation;
boolean more = false;
boolean started = false;
boolean ended = true;
t.clear();
for (int i = count - 1; i >= 0; --i) {
final Animation a = animations.get(i);
temp.clear();
more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
t.compose(temp);
started = started || a.hasStarted();
ended = a.hasEnded() && ended;
}
...
}
Properties like duration, fillBefore, fillAfter, and repeatMode can
be pushed down to child animations via property flags (lines 54-61):
// AnimationSet.java, lines 54-61
private static final int PROPERTY_FILL_AFTER_MASK = 0x1;
private static final int PROPERTY_FILL_BEFORE_MASK = 0x2;
private static final int PROPERTY_REPEAT_MODE_MASK = 0x4;
private static final int PROPERTY_START_OFFSET_MASK = 0x8;
private static final int PROPERTY_SHARE_INTERPOLATOR_MASK = 0x10;
private static final int PROPERTY_DURATION_MASK = 0x20;
private static final int PROPERTY_MORPH_MATRIX_MASK = 0x40;
private static final int PROPERTY_CHANGE_BOUNDS_MASK = 0x80;
14.2.6 Interpolators¶
The android.view.animation package provides 12 built-in interpolators:
| Interpolator | Formula / Behavior | Typical Use |
|---|---|---|
AccelerateDecelerateInterpolator |
cos((t+1)*PI)/2 + 0.5 |
Default; natural motion |
AccelerateInterpolator |
t^(2*factor) |
Exit animations |
DecelerateInterpolator |
1 - (1-t)^(2*factor) |
Enter animations |
LinearInterpolator |
t |
Constant velocity |
BounceInterpolator |
Piecewise quadratic | Bounce at end |
OvershootInterpolator |
Cubic overshoot | Spring-like |
AnticipateInterpolator |
Pull back then shoot | Cartoon wind-up |
AnticipateOvershootInterpolator |
Both anticipation and overshoot | Combined |
CycleInterpolator |
sin(2*PI*cycles*t) |
Shake/wiggle |
PathInterpolator |
Custom Bezier / SVG path | Material motion |
BackGestureInterpolator |
Back gesture curves | Predictive back |
BaseInterpolator |
Abstract base | Custom implementations |
The AccelerateDecelerateInterpolator formula is elegantly concise:
// frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java, line 39
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
14.2.7 PathInterpolator¶
Introduced in API 21 for Material Design motion, PathInterpolator maps
any Path from (0,0) to (1,1) into an interpolation curve. The path is
approximated into discrete (x,y) pairs, then binary search finds the y
value for any input t:
// frameworks/base/core/java/android/view/animation/PathInterpolator.java, lines 207-237
@Override
public float getInterpolation(float t) {
if (t <= 0) return 0;
else if (t >= 1) return 1;
// Binary search for the correct x to interpolate between.
int startIndex = 0;
int endIndex = mX.length - 1;
while (endIndex - startIndex > 1) {
int midIndex = (startIndex + endIndex) / 2;
if (t < mX[midIndex]) {
endIndex = midIndex;
} else {
startIndex = midIndex;
}
}
float xRange = mX[endIndex] - mX[startIndex];
if (xRange == 0) return mY[startIndex];
float tInRange = t - mX[startIndex];
float fraction = tInRange / xRange;
float startY = mY[startIndex];
float endY = mY[endIndex];
return startY + (fraction * (endY - startY));
}
Three construction modes are supported:
- Quadratic Bezier:
PathInterpolator(controlX, controlY)-- one control point - Cubic Bezier:
PathInterpolator(cx1, cy1, cx2, cy2)-- two control points - SVG Path Data: via
pathDataXML attribute, parsed throughPathParser
All interpolators implement NativeInterpolator to provide a native handle
for HWUI RenderThread animations.
14.2.8 View Animation Class Hierarchy¶
classDiagram
class Animation {
<<abstract>>
+applyTransformation(float, Transformation)*
+initialize(int, int, int, int)
+getTransformation(long, Transformation) boolean
+start()
+cancel()
+setDuration(long)
+setInterpolator(Interpolator)
+setRepeatCount(int)
+setRepeatMode(int)
+setFillAfter(boolean)
+setAnimationListener(AnimationListener)
}
class AlphaAnimation {
-float mFromAlpha
-float mToAlpha
}
class TranslateAnimation {
-float mFromXDelta
-float mToXDelta
-float mFromYDelta
-float mToYDelta
}
class RotateAnimation {
-float mFromDegrees
-float mToDegrees
-float mPivotX
-float mPivotY
}
class ScaleAnimation {
-float mFromX, mToX
-float mFromY, mToY
-float mPivotX, mPivotY
}
class AnimationSet {
-ArrayList~Animation~ mAnimations
+addAnimation(Animation)
}
class ClipRectAnimation
class ExtendAnimation
class TranslateXAnimation
class TranslateYAnimation
Animation <|-- AlphaAnimation
Animation <|-- TranslateAnimation
Animation <|-- RotateAnimation
Animation <|-- ScaleAnimation
Animation <|-- AnimationSet
Animation <|-- ClipRectAnimation
Animation <|-- ExtendAnimation
TranslateAnimation <|-- TranslateXAnimation
TranslateAnimation <|-- TranslateYAnimation
14.2.9 Limitations of View Animation¶
-
No property change: The animation applies a visual-only transformation. The view's
left,top,width,heightare unchanged, so hit testing uses the original bounds. -
View-only: Cannot animate arbitrary objects or non-View properties.
-
Limited types: Only matrix (translate/rotate/scale) and alpha. No color animations, no arbitrary typed values.
-
Composition by matrix multiplication: AnimationSet concatenates matrices, which limits complex multi-property coordination.
These limitations motivated the Property Animation framework in API 11.
14.2.10 The getTransformation() Core Loop¶
The heart of View Animation is the getTransformation() method that
computes the transformation for each frame. This is the complete algorithm
(lines 1011-1079):
// frameworks/base/core/java/android/view/animation/Animation.java, lines 1011-1079
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) {
mStartTime = currentTime;
}
final long startOffset = getStartOffset();
final long duration = mDuration;
float normalizedTime;
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
if (!mStarted) {
fireAnimationStart();
mStarted = true;
...
}
if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if (mCycleFlip) {
normalizedTime = 1.0f - normalizedTime;
}
getTransformationAt(normalizedTime, outTransformation);
}
if (expired) {
if (mRepeatCount == mRepeated || isCanceled()) {
if (!mEnded) {
mEnded = true;
guard.close();
fireAnimationEnd();
}
} else {
if (mRepeatCount > 0) {
mRepeated++;
}
if (mRepeatMode == REVERSE) {
mCycleFlip = !mCycleFlip;
}
mStartTime = -1;
mMore = true;
fireAnimationRepeat();
}
}
...
return mMore;
}
The algorithm breaks down into these steps:
-
Start time initialization: On the first call,
mStartTimeis set tocurrentTime, implementing theSTART_ON_FIRST_FRAMEbehavior. -
Normalized time computation:
normalizedTime = (currentTime - startTime - offset) / duration. This yields a value in [0, 1] representing progress through one cycle. -
Expiration check: If
normalizedTime >= 1.0, the current cycle is complete. -
Fill clamping: If
mFillEnabledis true, the normalized time is clamped to [0, 1] to prevent extrapolation. -
Cycle flip: In REVERSE repeat mode,
mCycleFlipalternates between true/false each repeat, and when true, the time is inverted:1.0 - normalizedTime. -
Transformation application:
getTransformationAt()applies the interpolator and calls the subclassapplyTransformation(). -
Repeat handling: If the animation has expired but the repeat count is not exhausted,
mStartTimeis reset to -1 andmMoreis set to true to continue on the next frame.
14.2.11 resolveSize: Value Type Resolution¶
The resolveSize() method (line 1185) converts animation values to pixels
based on their type:
// Animation.java, lines 1185-1196
protected float resolveSize(int type, float value, int size, int parentSize) {
switch (type) {
case ABSOLUTE:
return value;
case RELATIVE_TO_SELF:
return size * value;
case RELATIVE_TO_PARENT:
return parentSize * value;
default:
return value;
}
}
This enables XML declarations like android:fromXDelta="50%" (relative to
self) or android:fromXDelta="50%p" (relative to parent).
14.2.12 View Animation in Window Manager Context¶
View Animations are also used internally by the Window Manager for legacy
window transitions. WindowAnimationSpec wraps a view Animation to
apply it to a SurfaceControl instead of a View. The animation's
Transformation matrix is converted into SurfaceControl.Transaction
operations (setPosition, setMatrix, setAlpha).
14.2.13 Interpolator Native Bridge¶
All built-in interpolators implement NativeInterpolator, which provides
a createNativeInterpolator() method returning a native handle. This
handle is used by HWUI to run the same interpolation curve on the
RenderThread without crossing the JNI boundary per frame:
// AccelerateDecelerateInterpolator.java, lines 43-47
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactory.createAccelerateDecelerateInterpolator();
}
The native implementation in frameworks/base/libs/hwui/Interpolator.cpp
mirrors the Java formulas exactly, ensuring visual consistency between
UI-thread and RenderThread animations.
14.2.14 View Animation File Summary¶
| File | Lines | Purpose |
|---|---|---|
Animation.java |
1,363 | Abstract base class |
AnimationSet.java |
553 | Group of simultaneous animations |
AnimationUtils.java |
~400 | Loading helpers, currentAnimationTimeMillis |
Transformation.java |
~220 | Matrix + alpha container |
AlphaAnimation.java |
89 | Opacity animation |
TranslateAnimation.java |
241 | Position animation |
RotateAnimation.java |
183 | Rotation animation |
ScaleAnimation.java |
289 | Scale animation |
ClipRectAnimation.java |
~80 | Clip rect animation |
ExtendAnimation.java |
~60 | Edge extension animation |
TranslateXAnimation.java |
~40 | X-only translation (optimized) |
TranslateYAnimation.java |
~40 | Y-only translation (optimized) |
PathInterpolator.java |
245 | Bezier/path-based interpolation |
AccelerateDecelerateInterpolator.java |
48 | Default cosine ease |
AccelerateInterpolator.java |
~55 | Power-curve acceleration |
DecelerateInterpolator.java |
~55 | Power-curve deceleration |
LinearInterpolator.java |
~35 | Identity function |
BounceInterpolator.java |
~50 | Bounce at end |
OvershootInterpolator.java |
~60 | Cubic overshoot |
AnticipateInterpolator.java |
~55 | Wind-up before motion |
AnticipateOvershootInterpolator.java |
~70 | Combined wind-up and overshoot |
CycleInterpolator.java |
~45 | Sine cycle |
BackGestureInterpolator.java |
~60 | Back gesture curves |
BaseInterpolator.java |
~30 | Abstract base for interpolators |
Interpolator.java |
~10 | Interface extending TimeInterpolator |
LayoutAnimationController.java |
~350 | Staggered child animations |
GridLayoutAnimationController.java |
~200 | Grid-based staggered animations |
14.3 Property Animation¶
14.3.1 Overview¶
Introduced in Android 3.0 (API 11), the Property Animation framework is the
modern workhorse of Android animation. It animates actual properties on
any Java object -- not just views. When you animate View.setTranslationX,
the property genuinely changes, so hit testing, layout, and accessibility
all reflect the animated state.
Source directory:
frameworks/base/core/java/android/animation/ (31 files, ~13,400 lines)
14.3.2 Core Class Hierarchy¶
classDiagram
class Animator {
<<abstract>>
+start()
+cancel()
+end()
+pause()
+resume()
+setDuration(long) Animator
+setInterpolator(TimeInterpolator)
+addListener(AnimatorListener)
+isRunning() boolean
}
class ValueAnimator {
-long mDuration = 300
-long mStartDelay
-int mRepeatCount
-int mRepeatMode
-TimeInterpolator mInterpolator
-PropertyValuesHolder[] mValues
+ofInt(int...) ValueAnimator$
+ofFloat(float...) ValueAnimator$
+ofArgb(int...) ValueAnimator$
+ofObject(TypeEvaluator, Object...) ValueAnimator$
+ofPropertyValuesHolder(PropertyValuesHolder...) ValueAnimator$
+setEvaluator(TypeEvaluator)
+getAnimatedValue() Object
+addUpdateListener(AnimatorUpdateListener)
}
class ObjectAnimator {
-Object mTarget
-String mPropertyName
-Property mProperty
+ofFloat(Object, String, float...) ObjectAnimator$
+ofInt(Object, String, int...) ObjectAnimator$
+ofArgb(Object, String, int...) ObjectAnimator$
}
class AnimatorSet {
-ArrayList~Node~ mNodes
-ArrayMap~Animator,Node~ mNodeMap
+playTogether(Animator...)
+playSequentially(Animator...)
+play(Animator) Builder
}
class TimeAnimator {
+setTimeListener(TimeListener)
}
Animator <|-- ValueAnimator
ValueAnimator <|-- ObjectAnimator
Animator <|-- AnimatorSet
ValueAnimator <|-- TimeAnimator
14.3.3 ValueAnimator Deep Dive¶
ValueAnimator.java (1,821 lines) is the engine of property animation.
Key fields (lines 96-279):
// frameworks/base/core/java/android/animation/ValueAnimator.java
private static float sDurationScale = 1.0f; // System-wide scale (line 96)
long mStartTime = -1; // First frame time (line 115)
boolean mStartTimeCommitted; // Jank compensation flag (line 129)
float mSeekFraction = -1; // Seek position (line 135)
private long mDuration = 300; // Default 300ms (line 218)
private int mRepeatCount = 0; // Default: play once (line 226)
private int mRepeatMode = RESTART; // RESTART or REVERSE (line 234)
private TimeInterpolator mInterpolator = sDefaultInterpolator; // (line 253)
PropertyValuesHolder[] mValues; // Animated properties (line 263)
HashMap<String, PropertyValuesHolder> mValuesMap; // Name-to-PVH lookup (line 269)
Duration Scale: The system-wide sDurationScale multiplies all animation
durations. Developer Options > "Animator duration scale" modifies this.
When set to 0, areAnimatorsEnabled() returns false (line 411):
// ValueAnimator.java, line 410-412
public static boolean areAnimatorsEnabled() {
return !(sDurationScale == 0);
}
Factory methods (lines 433-515):
| Factory | Evaluator | Description |
|---|---|---|
ofInt(int...) |
IntEvaluator | Integer range |
ofFloat(float...) |
FloatEvaluator | Float range |
ofArgb(int...) |
ArgbEvaluator | Color interpolation in sRGB |
ofObject(TypeEvaluator, Object...) |
Custom | Arbitrary type |
ofPropertyValuesHolder(PVH...) |
Per-holder | Multi-property |
14.3.4 The Animation Frame Loop¶
When start() is called, ValueAnimator registers itself with
AnimationHandler as an AnimationFrameCallback. The handler schedules
a Choreographer frame callback. On each VSYNC:
sequenceDiagram
participant C as Choreographer
participant AH as AnimationHandler
participant VA as ValueAnimator
participant PVH as PropertyValuesHolder
participant KFS as KeyframeSet
participant TE as TypeEvaluator
participant Target as Target Object
C->>AH: mFrameCallback.doFrame(frameTimeNanos)
AH->>AH: doAnimationFrame(frameTime)
loop for each AnimationFrameCallback
AH->>VA: doAnimationFrame(frameTime)
VA->>VA: animateBasedOnTime(currentTime)
Note over VA: compute fraction from elapsed time
VA->>VA: animateValue(fraction)
Note over VA: apply interpolator to get interpolated fraction
loop for each PropertyValuesHolder
VA->>PVH: calculateValue(interpolatedFraction)
PVH->>KFS: getValue(fraction)
KFS->>TE: evaluate(fraction, startValue, endValue)
TE-->>PVH: interpolated value
end
VA->>VA: notify AnimatorUpdateListeners
end
The core timing logic in animateBasedOnTime() (simplified):
- Compute
currentIterationFraction = (currentTime - startTime) / duration - Handle repeat: divide by total iterations to get
overallFraction - For REVERSE mode, flip fraction on odd iterations
- Call
animateValue(fraction)which applies the interpolator
14.3.5 ObjectAnimator¶
ObjectAnimator (1,004 lines) extends ValueAnimator to set the animated
value directly on a target object. It resolves the target property through
two mechanisms:
-
Property name (String): Uses reflection to find
setFoo()/getFoo()methods. For best performance, optimized JNI paths exist forfloatandintreturn types. -
Property object: Uses the
Property<T, V>abstraction which avoids reflection entirely.
// frameworks/base/core/java/android/animation/ObjectAnimator.java, lines 69-80
public final class ObjectAnimator extends ValueAnimator {
private Object mTarget;
private String mPropertyName;
private Property mProperty;
private boolean mAutoCancel = false;
...
}
Common factory methods:
ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0f, 100f)-- preferred; no reflectionObjectAnimator.ofArgb(view, "backgroundColor", Color.RED, Color.BLUE)
14.3.6 PropertyValuesHolder¶
PropertyValuesHolder (1,729 lines) encapsulates one animated property:
its name/Property reference, the setter/getter methods, the keyframe set,
and the type evaluator.
// frameworks/base/core/java/android/animation/PropertyValuesHolder.java, lines 38-78
public class PropertyValuesHolder implements Cloneable {
String mPropertyName;
protected Property mProperty;
Method mSetter = null;
private Method mGetter = null;
Class mValueType;
Keyframes mKeyframes = null;
...
}
The class maintains static caches of setter/getter methods per class to avoid repeated reflection:
// PropertyValuesHolder.java, lines 92-97
private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class,
int.class, Double.class, Integer.class};
private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class,
double.class, Float.class, Double.class};
14.3.7 Keyframes and TypeEvaluators¶
The Keyframe class defines a value at a specific fraction (0.0 to 1.0).
KeyframeSet holds the ordered set and performs interpolation between
adjacent keyframes.
Built-in evaluators:
| Evaluator | Operation |
|---|---|
IntEvaluator |
startValue + (int)(fraction * (endValue - startValue)) |
FloatEvaluator |
startValue + fraction * (endValue - startValue) |
ArgbEvaluator |
Per-channel interpolation in sRGB color space |
PointFEvaluator |
Interpolates PointF x,y independently |
RectEvaluator |
Interpolates Rect left/top/right/bottom |
IntArrayEvaluator |
Element-wise int array interpolation |
FloatArrayEvaluator |
Element-wise float array interpolation |
14.3.8 AnimatorSet and the Dependency Graph¶
AnimatorSet (2,280 lines) organizes multiple Animator instances into
a dependency graph using a node-based internal structure:
graph LR
subgraph AnimatorSet
A[Node: fadeIn] -->|before| B[Node: moveRight]
A -->|before| C[Node: scaleUp]
B -->|before| D[Node: colorChange]
C -->|before| D
end
The Builder API chains dependencies:
AnimatorSet set = new AnimatorSet();
set.play(fadeIn).before(moveRight);
set.play(fadeIn).before(scaleUp);
set.play(moveRight).before(colorChange);
set.play(scaleUp).before(colorChange);
Internally, AnimatorSet uses an AnimationEvent list (line 90) sorted by
time. On each frame, it processes events whose time has arrived, starting
or ending child animators as needed.
14.3.9 AnimationHandler and Background Pausing¶
AnimationHandler (579 lines) manages the per-thread animation loop.
Key mechanism -- background pausing (lines 196-288): When all windows in
a process go to the background, AnimationHandler pauses all infinite-duration
animators to save CPU. It tracks visibility through mAnimatorRequestors:
// frameworks/base/core/java/android/animation/AnimationHandler.java, lines 272-288
private Choreographer.FrameCallback mPauser = frameTimeNanos -> {
if (mAnimatorRequestors.size() > 0) {
return; // something re-enabled since scheduling
}
for (int i = 0; i < mAnimationCallbacks.size(); ++i) {
AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback instanceof Animator) {
Animator animator = ((Animator) callback);
if (animator.getTotalDuration() == Animator.DURATION_INFINITE
&& !animator.isPaused()) {
mPausedAnimators.add(animator);
animator.pause();
}
}
}
};
14.3.10 ValueAnimator.start() Complete Flow¶
The start() method (lines 1117-1159) orchestrates the full animation
startup sequence. Here is the detailed flow:
flowchart TD
A[start called] --> B{Looper exists?}
B -->|No| ERR[throw AndroidRuntimeException]
B -->|Yes| C[Set mReversing, mStarted=true]
C --> D[mLastFrameTime = -1, mStartTime = -1]
D --> E[addAnimationCallback to AnimationHandler]
E --> F{startDelay == 0 OR seeked?}
F -->|Yes| G[startAnimation]
F -->|No| H[Wait for delay to elapse]
G --> I[initAnimation -- init all PropertyValuesHolders]
I --> J[mRunning = true]
J --> K[notifyStartListeners]
K --> L[setCurrentPlayTime 0]
L --> M[animateValue with fraction 0]
M --> N[Return -- first frame will come via Choreographer]
Key implementation detail -- addAnimationCallback(0) at line 1143 calls
through to AnimationHandler.addAnimationFrameCallback(), which:
- Adds this ValueAnimator to the
mAnimationCallbackslist - If this is the first callback, posts
mFrameCallbackto Choreographer - If there is a delay, stores the delay start time in
mDelayedCallbackStartTime
14.3.11 The animateBasedOnTime() Algorithm¶
This method (lines 1409-1434) is called each frame and converts wall-clock time to an animation fraction:
// ValueAnimator.java, lines 1409-1434
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
if (scaledDuration == 0) {
done = true;
} else if (newIteration && !lastIterationFinished) {
notifyListeners(AnimatorCaller.ON_REPEAT, false);
} else if (lastIterationFinished) {
done = true;
}
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
Note how getScaledDuration() applies the system-wide duration scale:
14.3.12 Jank Compensation: commitAnimationFrame¶
After the TRAVERSAL callback, Choreographer dispatches COMMIT callbacks. ValueAnimator registers a commit callback to compensate for jank:
// ValueAnimator.java, lines 1384-1395
public void commitAnimationFrame(long frameTime) {
if (!mStartTimeCommitted) {
mStartTimeCommitted = true;
long adjustment = frameTime - mLastFrameTime;
if (adjustment > 0) {
mStartTime += adjustment;
}
}
}
If the first frame of an animation is delayed by heavy layout work, the
commit callback adjusts mStartTime forward so the animation does not
appear to "jump" to a later position.
14.3.13 Duration Scale and Accessibility¶
The system-wide sDurationScale is modified by three settings:
- Developer Options > Animator duration scale: 0.5x, 1x, 2x, 5x, 10x
- Battery Saver mode: May set scale to 0 to disable all animations
- Programmatic:
ValueAnimator.setDurationScale()(hidden API)
When sDurationScale is 0, areAnimatorsEnabled() returns false, and
animations complete instantly. This is critical for:
- Accessibility testing (verifying UI works without animations)
- Performance testing (removing animation overhead)
- Battery conservation
Applications can listen for scale changes:
ValueAnimator.registerDurationScaleChangeListener(scale -> {
// Adjust behavior when animation scale changes
if (scale == 0) {
// Animations are disabled
}
});
14.3.14 ObjectAnimator AutoCancel¶
When mAutoCancel is true, starting an ObjectAnimator automatically cancels
any running ObjectAnimator targeting the same object and property:
ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 1f);
anim.setAutoCancel(true);
anim.start();
// Starting another alpha animation on the same view
// will cancel the first one automatically
This is the mechanism behind ViewPropertyAnimator's smooth cancellation --
each new view.animate().alpha() call cancels the previous alpha animation.
14.3.15 StateListAnimator¶
StateListAnimator maps view states (pressed, focused, selected, etc.) to
Animator objects, enabling state-driven animations. It is commonly used
for Material Design elevation changes:
<selector>
<item android:state_pressed="true">
<objectAnimator android:propertyName="translationZ"
android:duration="100" android:valueTo="6dp"/>
</item>
<item>
<objectAnimator android:propertyName="translationZ"
android:duration="100" android:valueTo="0dp"/>
</item>
</selector>
14.3.16 Property Animation File Summary¶
| File | Lines | Purpose |
|---|---|---|
Animator.java |
~850 | Abstract base for all animators |
ValueAnimator.java |
1,821 | Core timing engine |
ObjectAnimator.java |
1,004 | Property-targeting animator |
AnimatorSet.java |
2,280 | Multi-animator orchestration |
PropertyValuesHolder.java |
1,729 | Per-property value management |
AnimationHandler.java |
579 | Frame callback manager |
Keyframe.java |
~300 | Single time/value pair |
KeyframeSet.java |
~300 | Ordered keyframe collection |
FloatKeyframeSet.java |
~150 | Optimized float keyframes |
IntKeyframeSet.java |
~150 | Optimized int keyframes |
PathKeyframes.java |
~200 | Path-based keyframes |
ArgbEvaluator.java |
~90 | Color interpolation |
FloatEvaluator.java |
~40 | Float interpolation |
IntEvaluator.java |
~40 | Integer interpolation |
PointFEvaluator.java |
~60 | PointF interpolation |
RectEvaluator.java |
~70 | Rect interpolation |
LayoutTransition.java |
~1,000 | ViewGroup layout change animation |
AnimatorInflater.java |
~700 | XML resource loading |
TimeAnimator.java |
~100 | Raw frame timing |
RevealAnimator.java |
~60 | Circular reveal support |
StateListAnimator.java |
~250 | State-driven animations |
TypeConverter.java |
~60 | Type conversion support |
BidirectionalTypeConverter.java |
~40 | Two-way conversion |
14.3.17 AnimationHandler.doAnimationFrame() Deep Dive¶
The per-frame animation dispatch (lines 395-416) is the core of the animation loop:
// AnimationHandler.java, lines 395-416
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
Key details:
- Null checking: Callbacks may be nulled out by
removeCallback()while iterating. The list is cleaned up at the end. - Delay checking:
isCallbackDue()checks if the start delay has elapsed by comparing againstmDelayedCallbackStartTime. - Commit callback: If the animation has registered for commit timing (for jank compensation), a commit callback is posted to run after traversals.
14.3.18 AnimationFrameCallbackProvider¶
The AnimationHandler uses a pluggable callback provider for its timing
source. The default implementation wraps Choreographer:
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
@Override
public void postCommitCallback(Runnable runnable) {
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
}
@Override
public long getFrameTime() {
return mChoreographer.getFrameTime();
}
@Override
public long getFrameDelay() {
return Choreographer.getFrameDelay();
}
@Override
public void setFrameDelay(long delay) {
Choreographer.setFrameDelay(delay);
}
}
For testing, a custom provider can replace Choreographer with a manual clock, enabling deterministic animation testing.
14.3.19 Auto-Cancel in AnimationHandler¶
When a new ObjectAnimator starts with setAutoCancel(true),
AnimationHandler.autoCancelBasedOn() (line 466) scans all running
callbacks and cancels any ObjectAnimator that targets the same property
on the same object:
// AnimationHandler.java, lines 466-476
void autoCancelBasedOn(ObjectAnimator objectAnimator) {
for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
AnimationFrameCallback cb = mAnimationCallbacks.get(i);
if (cb == null) {
continue;
}
if (objectAnimator.shouldAutoCancel(cb)) {
((Animator) mAnimationCallbacks.get(i)).cancel();
}
}
}
This prevents the common bug of multiple conflicting animators competing to set the same property.
14.3.20 AnimatorSet Node and Event System¶
AnimatorSet uses an internal Node class to represent each child animator
in the dependency graph. The Builder API constructs relationships between
nodes:
// AnimatorSet internal structure
class Node implements Cloneable {
Animator mAnimation;
ArrayList<Node> mChildNodes = null;
boolean mEnded = false;
ArrayList<Node> mSiblings; // "with" relationships
ArrayList<Node> mParents; // "after" dependencies
}
class AnimationEvent {
static final int ANIMATION_START = 0;
static final int ANIMATION_DELAY_ENDED = 1;
static final int ANIMATION_END = 2;
Node mNode;
int mEvent;
}
The mEvents list contains all start and end events sorted by time.
During animation, AnimatorSet walks this list and triggers events as their
times arrive.
graph TD
subgraph "AnimatorSet Internal Graph"
R[Root Node - delay animator]
R --> A[Node A - fadeIn]
R --> B[Node B - moveRight]
A --> C[Node C - scaleUp - after A]
B --> C
C --> D[Node D - colorChange - after C]
end
subgraph "Events Timeline"
E1["t=0: A start, B start"] --> E2["t=300ms: A end"]
E2 --> E3["t=300ms: C start"]
E3 --> E4["t=500ms: B end"]
E4 --> E5["t=600ms: C end"]
E5 --> E6["t=600ms: D start"]
E6 --> E7["t=900ms: D end"]
end
14.3.21 LayoutTransition¶
LayoutTransition (part of android.animation) provides automatic
animations when views are added to or removed from a ViewGroup. It defines
five animation types:
| Constant | When | Default Animation |
|---|---|---|
APPEARING |
View becomes visible | Fade in (alpha 0 to 1) |
DISAPPEARING |
View becomes invisible | Fade out (alpha 1 to 0) |
CHANGE_APPEARING |
Others move to make room | Bounds change |
CHANGE_DISAPPEARING |
Others fill gap | Bounds change |
CHANGING |
Layout change without add/remove | Bounds change |
By default, DISAPPEARING and CHANGE_APPEARING begin immediately;
APPEARING and CHANGE_DISAPPEARING begin after the default duration,
creating a natural sequencing effect.
14.4 Transition Framework¶
14.4.1 Overview¶
The Transition Framework (API 19+) automates the detection of property changes between two states of a view hierarchy ("scenes") and creates appropriate animations. Rather than manually calculating from/to values, developers describe what to transition and the framework figures out how.
Source directory:
frameworks/base/core/java/android/transition/ (33 files, ~9,200 lines)
14.4.2 Core Concepts¶
graph TD
A[Scene A - Start State] --> B[TransitionManager.go or beginDelayedTransition]
B --> C[Capture Start Values]
C --> D[Apply Scene Change]
D --> E[Capture End Values]
E --> F[Diff Start vs End]
F --> G[Create Animators for differences]
G --> H[Run Animations]
H --> I[Scene B - End State]
14.4.3 Transition Base Class¶
Transition.java (2,451 lines) is the abstract base. Each subclass must
implement three methods:
captureStartValues(TransitionValues)-- Record property values before the scene changecaptureEndValues(TransitionValues)-- Record property values after the scene changecreateAnimator(ViewGroup, TransitionValues, TransitionValues)-- Return anAnimatorfor the detected change
TransitionValues is a simple holder:
public class TransitionValues {
public View view;
public final Map<String, Object> values = new ArrayMap<>();
}
14.4.4 Built-in Transitions¶
classDiagram
class Transition {
<<abstract>>
+captureStartValues(TransitionValues)*
+captureEndValues(TransitionValues)*
+createAnimator(ViewGroup, TransitionValues, TransitionValues)* Animator
+setDuration(long) Transition
+setInterpolator(TimeInterpolator) Transition
+addTarget(View) Transition
+excludeTarget(View, boolean) Transition
}
class Visibility {
<<abstract>>
+onAppear(ViewGroup, View, TransitionValues, TransitionValues) Animator
+onDisappear(ViewGroup, View, TransitionValues, TransitionValues) Animator
}
class Fade {
+IN : int
+OUT : int
}
class Slide
class Explode
class ChangeBounds {
-PROPNAME_BOUNDS
-PROPNAME_CLIP
-PROPNAME_PARENT
}
class ChangeTransform
class ChangeClipBounds
class ChangeImageTransform
class ChangeScroll
class Crossfade
class Recolor
class Rotate
class TransitionSet {
+ORDERING_TOGETHER : int
+ORDERING_SEQUENTIAL : int
+addTransition(Transition) TransitionSet
}
class AutoTransition
Transition <|-- Visibility
Transition <|-- ChangeBounds
Transition <|-- ChangeTransform
Transition <|-- ChangeClipBounds
Transition <|-- ChangeImageTransform
Transition <|-- ChangeScroll
Transition <|-- Crossfade
Transition <|-- Recolor
Transition <|-- Rotate
Transition <|-- TransitionSet
Visibility <|-- Fade
Visibility <|-- Slide
Visibility <|-- Explode
TransitionSet <|-- AutoTransition
14.4.5 ChangeBounds¶
ChangeBounds (the most complex built-in transition) captures five
properties:
// frameworks/base/core/java/android/transition/ChangeBounds.java, lines 58-69
private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
private static final String PROPNAME_CLIP = "android:changeBounds:clip";
private static final String PROPNAME_PARENT = "android:changeBounds:parent";
private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
It creates an AnimatorSet that animates view bounds using ObjectAnimator
on custom Property objects (TOP_LEFT_PROPERTY, BOTTOM_RIGHT_PROPERTY)
which internally call View.setLeft(), View.setTop(), etc.
14.4.6 Fade¶
Fade extends Visibility to animate alpha changes:
// frameworks/base/core/java/android/transition/Fade.java, lines 61-99
public class Fade extends Visibility {
static final String PROPNAME_TRANSITION_ALPHA = "android:fade:transitionAlpha";
public static final int IN = Visibility.MODE_IN;
public static final int OUT = Visibility.MODE_OUT;
...
}
The Visibility base class handles the complex logic of detecting whether
a view appeared (became VISIBLE or was added) or disappeared (became
GONE/INVISIBLE or was removed). For disappearing views, it uses
ViewGroupOverlay to keep the view visible during the fade-out.
14.4.7 TransitionManager¶
TransitionManager (470 lines) is the entry point for running transitions.
The most common API:
// In-place transition on current hierarchy
TransitionManager.beginDelayedTransition(viewGroup, new AutoTransition());
// ... modify views ...
// Framework captures end values on next layout pass and runs animations
The default transition is AutoTransition, which is a TransitionSet
containing Fade(OUT), ChangeBounds, and Fade(IN) in sequence.
14.4.8 Scene¶
Scene represents a snapshot of a view hierarchy. It can be created from
a layout resource or captured from the current state:
Scene scene = Scene.getSceneForLayout(sceneRoot, R.layout.scene_b, context);
TransitionManager.go(scene, new ChangeBounds());
14.4.9 Transition Matching Algorithm¶
A critical aspect of the Transition Framework is how it matches views between
the start and end states. The Transition class defines four match
strategies, applied in a configurable order:
// frameworks/base/core/java/android/transition/Transition.java, lines 131-167
public static final int MATCH_INSTANCE = 0x1; // Same View object
public static final int MATCH_NAME = 0x2; // Same transitionName
public static final int MATCH_ID = 0x3; // Same view ID
public static final int MATCH_ITEM_ID = 0x4; // Same adapter item ID
private static final int[] DEFAULT_MATCH_ORDER = {
MATCH_NAME,
MATCH_INSTANCE,
MATCH_ID,
MATCH_ITEM_ID,
};
The default order is: transition name first, then instance, then ID, then item ID. This order matters because once a view in the start state is matched with a view in the end state, both are removed from the pool of unmatched views.
flowchart TD
A[Start: Collect all start views] --> B[End: Collect all end views]
B --> C[Match by MATCH_NAME]
C --> D[Match by MATCH_INSTANCE]
D --> E[Match by MATCH_ID]
E --> F[Match by MATCH_ITEM_ID]
F --> G[Remaining unmatched start views -> appeared/disappeared]
G --> H[Create animators for each matched pair]
14.4.10 Transition Internal State¶
The Transition base class maintains extensive internal state (lines 179-252):
// Transition.java, lines 179-252 (key fields)
private String mName = getClass().getName();
long mStartDelay = -1;
long mDuration = -1;
TimeInterpolator mInterpolator = null;
ArrayList<Integer> mTargetIds = new ArrayList<>();
ArrayList<View> mTargets = new ArrayList<>();
ArrayList<String> mTargetNames = null;
ArrayList<Class> mTargetTypes = null;
// ... exclude lists ...
private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
TransitionSet mParent = null;
int[] mMatchOrder = DEFAULT_MATCH_ORDER;
ArrayList<Animator> mCurrentAnimators = new ArrayList<>();
TransitionPropagation mPropagation;
EpicenterCallback mEpicenterCallback;
PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
Note that duration, startDelay, and interpolator all default to -1/null, which means "use the animator's own values." Only if explicitly set on the Transition will they override the child animators.
14.4.11 The TransitionValues Container¶
For each view, captureStartValues() and captureEndValues() populate a
TransitionValues map. The convention is to use fully-qualified keys:
// In ChangeBounds:
private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
private static final String PROPNAME_CLIP = "android:changeBounds:clip";
private static final String PROPNAME_PARENT = "android:changeBounds:parent";
private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
This namespacing prevents collisions when multiple transitions capture values for the same view.
14.4.12 TransitionSet Ordering¶
TransitionSet can run child transitions together or sequentially:
// Together (default) - all children run simultaneously
TransitionSet set = new TransitionSet();
set.setOrdering(TransitionSet.ORDERING_TOGETHER);
set.addTransition(new Fade(Fade.OUT));
set.addTransition(new ChangeBounds());
set.addTransition(new Fade(Fade.IN));
// Sequential - children run one after another
TransitionSet seq = new TransitionSet();
seq.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
seq.addTransition(new Fade(Fade.OUT)); // First: fade out
seq.addTransition(new ChangeBounds()); // Then: move
seq.addTransition(new Fade(Fade.IN)); // Finally: fade in
AutoTransition is a pre-built sequential TransitionSet:
// AutoTransition = Fade(OUT) -> ChangeBounds -> Fade(IN) (sequential)
public class AutoTransition extends TransitionSet {
public AutoTransition() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT));
addTransition(new ChangeBounds());
addTransition(new Fade(Fade.IN));
}
}
14.4.13 Target Filtering¶
Transitions can be targeted to specific views:
transition.addTarget(R.id.my_view); // By ID
transition.addTarget("hero_image"); // By transition name
transition.addTarget(TextView.class); // By class
transition.addTarget(specificView); // By instance
transition.excludeTarget(R.id.toolbar, true); // Exclude by ID
transition.excludeTarget(Button.class, true); // Exclude by class
transition.excludeChildren(R.id.list, true); // Exclude subtree
When no targets are specified, the transition operates on all views in the scene root.
14.4.14 Explode and Slide¶
Explode extends Visibility and moves views outward from (or inward to)
an epicenter point. It uses CircularPropagation to stagger the animations
so views further from the center start later:
graph TD
subgraph "Explode Transition"
CENTER[Epicenter] --> A[View A - short delay]
CENTER --> B[View B - medium delay]
CENTER --> C[View C - long delay]
CENTER --> D[View D - longest delay]
end
Slide moves views from/to a specified edge (top, bottom, left, right)
and uses SidePropagation to create a wave effect.
14.4.15 Propagation and Motion Paths¶
TransitionPropagation controls the order in which targets animate during a transition. Built-in propagations:
CircularPropagation-- Radiates from a center point (used byExplode)SidePropagation-- Propagates from an edge (used bySlide)
PathMotion controls the path that animated properties follow:
ArcMotion-- Curved arc between start and end positionsPatternPathMotion-- Custom path pattern
14.4.16 Transition Framework Architecture¶
sequenceDiagram
participant App
participant TM as TransitionManager
participant T as Transition
participant VG as ViewGroup
participant VTO as ViewTreeObserver
App->>TM: beginDelayedTransition(viewGroup, transition)
TM->>T: captureStartValues() for all target views
TM->>VTO: addOnPreDrawListener
App->>VG: modify views (add, remove, change properties)
Note over VG: Layout pass happens
VTO->>TM: onPreDraw callback
TM->>T: captureEndValues() for all target views
TM->>T: createAnimators() - diff start vs end
T-->>TM: List of Animator objects
TM->>TM: runAnimators()
Note over TM: Animations play on UI thread
14.5 Activity Transitions¶
14.5.1 Overview¶
Activity transitions (API 21+) extend the Transition Framework across activity boundaries, enabling shared element animations between activities or fragments. The system coordinates the capture and transfer of shared element state between the calling and called activities.
Key source files:
frameworks/base/core/java/android/app/ActivityOptions.java(~3,085 lines)frameworks/base/core/java/android/app/ActivityTransitionCoordinator.java(~1,122 lines)frameworks/base/core/java/android/app/EnterTransitionCoordinator.javaframeworks/base/core/java/android/app/ExitTransitionCoordinator.java
14.5.2 Transition Types¶
An activity transition comprises up to four independent animations:
graph LR
subgraph "Calling Activity (Exit)"
A[Exit Transition] --> B[Shared Element Exit]
end
subgraph "Called Activity (Enter)"
C[Enter Transition] --> D[Shared Element Enter]
end
A -.->|coordinates| C
B -.->|shared state transfer| D
| Animation | Default | Purpose |
|---|---|---|
| Exit Transition | null (no animation) | Non-shared views in calling activity |
| Enter Transition | null | Non-shared views in called activity |
| Shared Element Exit | ChangeTransform + ChangeBounds |
Shared elements leaving |
| Shared Element Enter | ChangeTransform + ChangeBounds |
Shared elements arriving |
| Return Transition | Reverse of Enter | Going back |
| Reenter Transition | Reverse of Exit | Returning to calling |
14.5.3 Shared Element Coordination¶
The system transfers shared element state through a Bundle containing:
- View name (the
transitionName) - Screen position and size
- Bitmap snapshot (for cross-process transfers)
- View-specific extras (e.g.,
ImageViewscale type and matrix)
sequenceDiagram
participant CallingAct as Calling Activity
participant WM as WindowManager
participant CalledAct as Called Activity
CallingAct->>CallingAct: captureSharedElementState()
CallingAct->>WM: startActivity with ActivityOptions
Note over WM: Bundle with shared element state
WM->>CalledAct: onCreate with shared element bundle
CalledAct->>CalledAct: postponeEnterTransition()
Note over CalledAct: Load data, set up views
CalledAct->>CalledAct: startPostponedEnterTransition()
CalledAct->>CalledAct: mapSharedElements()
CalledAct->>CalledAct: Create enter transition animators
Note over CalledAct: Shared elements animate from<br/>calling position to final position
14.5.4 Shared Element Return Animation Detail¶
When the user presses back, the shared element return animation reverses the enter animation. The system handles this automatically, but developers can customize it:
// Override default return shared element transition
getWindow().setSharedElementReturnTransition(
new TransitionSet()
.addTransition(new ChangeBounds().setDuration(300))
.addTransition(new ChangeTransform())
.addTransition(new ChangeImageTransform())
);
The return animation captures the current state of shared elements in the called activity and animates them back to their position in the calling activity. This requires the calling activity to still be alive (not destroyed), which is usually the case for standard back navigation.
14.5.5 Transition Fragment Integration¶
Fragments use the same shared element mechanism but with additional complexity for fragment-to-fragment transitions:
Fragment fragmentB = new DetailFragment();
fragmentB.setSharedElementEnterTransition(new ChangeTransform());
getSupportFragmentManager()
.beginTransaction()
.addSharedElement(sharedImageView, "hero_image")
.replace(R.id.container, fragmentB)
.addToBackStack(null)
.commit();
The FragmentManager coordinates with the Transition Framework to capture shared element state before and after the fragment swap.
14.5.6 ActivityOptions Animation Types¶
ActivityOptions defines numerous animation styles through constants:
| Constant | Value | Description |
|---|---|---|
ANIM_NONE |
0 | No animation |
ANIM_CUSTOM |
1 | Custom window animation resource |
ANIM_SCALE_UP |
2 | Scale up from a rect |
ANIM_THUMBNAIL_SCALE_UP |
3 | Scale up from a thumbnail |
ANIM_THUMBNAIL_SCALE_DOWN |
4 | Scale down to a thumbnail |
ANIM_SCENE_TRANSITION |
5 | Shared element scene transition |
ANIM_CLIP_REVEAL |
6 | Circular reveal clip |
ANIM_OPEN_CROSS_PROFILE_APPS |
7 | Cross-profile app launch |
ANIM_FROM_STYLE |
8 | From window animation style |
14.5.7 ActivityTransitionCoordinator¶
The ActivityTransitionCoordinator (approximately 1,122 lines) manages the
complex handoff of shared element state between activities. It handles:
- View mapping: Matching shared element names between activities
- State capture: Recording position, size, visibility, and appearance
- Thumbnail generation: Creating bitmap snapshots for cross-process transfer
- Animation orchestration: Coordinating enter/exit/shared element timing
classDiagram
class ActivityTransitionCoordinator {
<<abstract>>
#ArrayList~String~ mAllSharedElementNames
#ArrayList~View~ mSharedElements
#ArrayList~String~ mSharedElementNames
#ViewGroup mDecor
#boolean mIsReturning
+getAcceptedNames() ArrayList
+getMappedNames() ArrayList
}
class ExitTransitionCoordinator {
+startExit()
+startExit(int, Intent)
+stop()
}
class EnterTransitionCoordinator {
+viewsReady(ArrayMap)
+onTriggerEnter()
}
ActivityTransitionCoordinator <|-- ExitTransitionCoordinator
ActivityTransitionCoordinator <|-- EnterTransitionCoordinator
14.5.8 Postponed Enter Transition¶
Activities can postpone their enter transition until data is loaded:
// In called Activity's onCreate:
postponeEnterTransition();
// After data is loaded and views are ready:
imageView.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
This is essential when shared elements depend on asynchronously loaded data (e.g., images loaded from network). Without postponement, the transition would animate to/from the wrong position or size.
14.5.9 Return and Reenter Transitions¶
When navigating back, the transitions can be reversed or customized:
| Direction | Called Activity | Calling Activity |
|---|---|---|
| Forward | Enter Transition | Exit Transition |
| Forward shared | Shared Element Enter | Shared Element Exit |
| Return | Return Transition (default: reverse of Enter) | Reenter Transition (default: reverse of Exit) |
| Return shared | Shared Element Return (default: reverse of Enter) |
Setting explicit return transitions enables asymmetric animations:
// In the called Activity:
getWindow().setReturnTransition(new Slide(Gravity.BOTTOM));
// On back, slides down instead of reversing the enter
14.6 Window Manager Animations¶
14.6.1 Overview¶
The Window Manager (WM) in system_server orchestrates animations for window-level operations: app launches, task switches, screen rotation, and more. These run on dedicated animation threads, independent of the application's UI thread.
Key source files in frameworks/base/services/core/java/com/android/server/wm/:
| File | Lines | Purpose |
|---|---|---|
WindowAnimator.java |
365 | Per-frame animation dispatch |
SurfaceAnimator.java |
647 | Leash-based surface animation |
SurfaceAnimationRunner.java |
359 | Lock-free animation execution |
WindowAnimationSpec.java |
~300 | Wraps legacy Animation for surfaces |
LocalAnimationAdapter.java |
~180 | Adapter for local animations |
AnimationAdapter.java |
~100 | Interface for animation implementations |
WindowStateAnimator.java |
~800 | Per-window animation state |
14.6.2 SurfaceAnimator and the Leash Pattern¶
The SurfaceAnimator (647 lines) implements a key architectural pattern:
the animation leash. Instead of directly animating a window's surface,
it creates a temporary parent surface (the "leash"), reparents the window's
children onto the leash, and hands the leash to the animation system:
// frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java, lines 44-51
/**
* A class that can run animations on objects that have a set of child surfaces.
* We do this by reparenting all child surfaces of an object onto a new surface,
* called the "Leash". The Leash gets attached in the surface hierarchy where
* the children were attached to. We then hand off the Leash to the component
* handling the animation, which is specified by the AnimationAdapter.
*/
graph TD
subgraph "Before Animation"
P1[Parent Surface] --> W1[Window Surface]
W1 --> C1[Child 1]
W1 --> C2[Child 2]
end
subgraph "During Animation"
P2[Parent Surface] --> L[Animation Leash]
L --> W2[Window Surface]
W2 --> C3[Child 1]
W2 --> C4[Child 2]
end
subgraph "After Animation"
P3[Parent Surface] --> W3[Window Surface]
W3 --> C5[Child 1]
W3 --> C6[Child 2]
end
This pattern prevents the animation from interfering with the window's internal surface tree. When the animation completes, children are reparented back to their original parent and the leash is destroyed.
14.6.3 SurfaceAnimationRunner¶
SurfaceAnimationRunner (359 lines) executes animations without holding
the WindowManager lock. This is critical for performance -- the WM lock
is heavily contended, and holding it during animation would cause jank:
// frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java, lines 44-47
/**
* Class to run animations without holding the window manager lock.
*/
class SurfaceAnimationRunner {
...
private final Handler mAnimationThreadHandler = AnimationThread.getHandler();
private final Handler mSurfaceAnimationHandler = SurfaceAnimationThread.getHandler();
...
}
It uses SfVsyncFrameCallbackProvider to synchronize with SurfaceFlinger's
VSYNC (not the app's VSYNC), ensuring animations are timed to the compositor's
frame rate.
14.6.4 WindowAnimator¶
WindowAnimator (365 lines) is the per-frame dispatch coordinator. It
schedules Choreographer callbacks and manages the overall animation state:
// frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java, lines 47-73
public class WindowAnimator {
final WindowManagerService mService;
final Choreographer.FrameCallback mAnimationFrameCallback;
final Choreographer.VsyncCallback mAnimationVsyncCallback;
long mCurrentTime;
private Choreographer mChoreographer;
...
}
14.6.5 SurfaceAnimator.startAnimation() Flow¶
The startAnimation() method (lines 166-197) orchestrates the leash creation
and animation launch:
// SurfaceAnimator.java, lines 166-197
void startAnimation(@NonNull Transaction t, @NonNull AnimationAdapter anim, boolean hidden,
@AnimationType int type,
@Nullable OnAnimationFinishedCallback animationFinishedCallback,
@Nullable Runnable animationCancelledCallback,
@Nullable AnimationAdapter snapshotAnim) {
cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
mAnimation = anim;
mAnimationType = type;
mSurfaceAnimationFinishedCallback = animationFinishedCallback;
mAnimationCancelledCallback = animationCancelledCallback;
final SurfaceControl surface = mAnimatable.getSurfaceControl();
if (surface == null) {
Slog.w(TAG, "Unable to start animation, surface is null or no children.");
cancelAnimation();
return;
}
if (mLeash == null) {
mLeash = createAnimationLeash(mAnimatable, surface, t, type,
mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(),
0 /* x */, 0 /* y */, hidden, mService.mTransactionFactory);
mAnimatable.onAnimationLeashCreated(t, mLeash);
}
mAnimatable.onLeashAnimationStarting(t, mLeash);
mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
...
}
Key steps:
- Cancel existing: Any running animation is cancelled first
- Null check: If the surface has been destroyed, bail out
- Create leash: A new surface is created and the original surface is reparented under it
- Notify animatable: The container gets a chance to adjust the leash
- Start animation: The
AnimationAdaptertakes control of the leash
14.6.6 Animation Transfer¶
When a window moves between containers (e.g., during a task stack change),
the animation needs to transfer to the new container without interruption.
transferAnimation() (line 267) handles this by moving the leash and
animation reference from one SurfaceAnimator to another.
14.6.7 Animation Types¶
SurfaceAnimator tracks the type of animation for proper cancellation and priority handling:
| Type | Usage |
|---|---|
ANIMATION_TYPE_NONE |
No animation |
ANIMATION_TYPE_APP_TRANSITION |
App open/close transition |
ANIMATION_TYPE_SCREEN_ROTATION |
Screen rotation animation |
ANIMATION_TYPE_RECENTS |
Recents animation |
ANIMATION_TYPE_WINDOW_ANIMATION |
Window-level animation |
ANIMATION_TYPE_DIMMER |
Dimmer fade in/out |
ANIMATION_TYPE_ALL |
Bitmask for all types |
14.6.8 WM Animation Architecture¶
graph TD
subgraph "System Server"
WMS[WindowManagerService] --> WA[WindowAnimator]
WA --> SA[SurfaceAnimator]
SA --> |creates leash| SAR[SurfaceAnimationRunner]
SAR --> |ValueAnimator on AnimationThread| AT[AnimationThread]
AT --> |SurfaceControl.Transaction| SF[SurfaceFlinger]
end
subgraph "Animation Types"
LA[LocalAnimationAdapter] --> |WindowAnimationSpec| SAR
RA[RemoteAnimationAdapter] --> |cross-process| SAR
end
14.6.9 WM Server-Side Transition (Transition.java in wm/)¶
The WM's Transition.java (distinct from the framework's
android.transition.Transition) manages the server-side state machine for
shell transitions. At approximately 4,587 lines, it tracks:
- Participating windows and tasks
- Transition type (open, close, change, etc.)
- Ready state and sync barriers
- Animation state for each participant
The TransitionController (approximately 2,049 lines) manages the lifecycle
of all active transitions and coordinates with the Shell process.
14.7 Shell Transition Animations¶
14.7.1 Overview¶
Shell Transitions (introduced in Android 12L) move transition animation logic out of system_server and into the Shell process. This architecture gives the SystemUI/Shell process direct control over how windows animate, enabling more sophisticated and customizable transitions.
Source directory:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/
(19 files, ~8,200 lines)
14.7.2 Architecture¶
sequenceDiagram
participant App as Application
participant WMCore as WM Core (system_server)
participant TC as TransitionController
participant Shell as Shell Process
participant Trans as Transitions.java
participant Handler as TransitionHandler
participant SF as SurfaceFlinger
App->>WMCore: startActivity / finish / etc
WMCore->>TC: requestTransition
TC->>TC: collect participating windows
TC->>TC: sync window draws
TC->>Shell: onTransitionReady(TransitionInfo)
Shell->>Trans: dispatchTransition
Trans->>Handler: startAnimation(TransitionInfo, SurfaceControl.Transaction)
Handler->>Handler: create animations
Handler->>SF: SurfaceControl.Transaction per frame
Handler->>Trans: onTransitionFinished
Trans->>WMCore: finishTransition
14.7.3 Transitions.java¶
Transitions.java (1,964 lines) is the central coordinator in the Shell
process. It implements ITransitionPlayer and receives transition callbacks
from the WindowManager core:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
public class Transitions implements ... RemoteCallable<Transitions> {
...
}
It maintains an ordered list of TransitionHandler implementations and
dispatches each transition to the first handler that claims it.
14.7.4 Handler Dispatch Chain¶
graph TD
T[Transitions.java] --> A{MixedTransitionHandler?}
A -->|yes| B[MixedTransitionHandler]
A -->|no| C{KeyguardTransitionHandler?}
C -->|yes| D[KeyguardTransitionHandler]
C -->|no| E{PipTransitionHandler?}
E -->|yes| F[PipTransitionHandler]
E -->|no| G{RemoteTransitionHandler?}
G -->|yes| H[RemoteTransitionHandler]
G -->|no| I[DefaultTransitionHandler]
14.7.5 DefaultTransitionHandler¶
DefaultTransitionHandler (1,081 lines) handles the common cases: app
launches, task switches, and activity closes. It loads window animations
from resources and applies them to SurfaceControl leashes:
// DefaultTransitionHandler.java (imports, lines 19-70)
static imports:
ANIM_CLIP_REVEAL, ANIM_CUSTOM, ANIM_FROM_STYLE, ANIM_NONE,
ANIM_OPEN_CROSS_PROFILE_APPS, ANIM_SCALE_UP, ANIM_SCENE_TRANSITION,
ANIM_THUMBNAIL_SCALE_DOWN, ANIM_THUMBNAIL_SCALE_UP
It builds surface animations using TransitionAnimationHelper.loadAttributeAnimation()
to resolve the correct window animation resource based on transition type and
window configuration.
14.7.6 RemoteTransitionHandler¶
Allows third-party launchers and apps to provide custom transition animations
by registering RemoteTransition objects. The Shell dispatches the transition
info and surface controls to the remote process, which runs the animation
and signals completion.
14.7.7 Mixed Transitions¶
MixedTransitionHandler handles cases where multiple transition types
overlap (e.g., pip + app launch). It splits the transition into independent
parts and delegates each to the appropriate handler.
14.7.8 Transition Types and Flags¶
The Shell processes these transition types from WindowManager:
| Type Constant | Value | Description |
|---|---|---|
TRANSIT_OPEN |
1 | An app window is opening |
TRANSIT_CLOSE |
2 | An app window is closing |
TRANSIT_TO_FRONT |
3 | Existing window brought to front |
TRANSIT_TO_BACK |
4 | Window sent to back |
TRANSIT_CHANGE |
6 | Window config change (resize, etc.) |
TRANSIT_KEYGUARD_OCCLUDE |
8 | Keyguard being occluded |
TRANSIT_KEYGUARD_UNOCCLUDE |
9 | Keyguard being unoccluded |
TRANSIT_SLEEP |
12 | Device going to sleep |
TRANSIT_FIRST_CUSTOM |
1000 | Start of custom transition range |
Each participant in a transition carries flags:
| Flag | Purpose |
|---|---|
FLAG_IS_WALLPAPER |
Participant is wallpaper |
FLAG_IS_DISPLAY |
Participant is the display |
FLAG_NO_ANIMATION |
Skip animation for this participant |
FLAG_TRANSLUCENT |
Participant is translucent |
FLAG_SHOW_WALLPAPER |
Wallpaper should be visible |
FLAG_FILLS_TASK |
Participant fills its task |
FLAG_IS_BEHIND_STARTING_WINDOW |
Behind a starting window |
FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT |
Receiving a starting window |
FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY |
In a task with embedded activities |
FLAG_BACK_GESTURE_ANIMATED |
Being animated by back gesture |
14.7.9 TransitionAnimationHelper¶
TransitionAnimationHelper provides utility methods for loading and
configuring transition animations:
loadAttributeAnimation()-- Loads the correct window animation from theme attributes based on transition typegetTransitionBackgroundColorIfSet()-- Extracts backdrop color from animation attributesisCoveredByOpaqueFullscreenChange()-- Determines if a change is hidden behind a fullscreen opaque window (skip animation)
14.7.10 Shell Transition Lifecycle¶
stateDiagram-v2
[*] --> Collecting: requestTransition
Collecting --> Syncing: all participants identified
Syncing --> Ready: all surfaces drawn
Ready --> Dispatched: onTransitionReady sent to Shell
Dispatched --> Animating: handler.startAnimation
Animating --> Finishing: animations complete
Finishing --> Merged: merged with next transition
Finishing --> Done: finishTransition
Done --> [*]
Animating --> Aborted: new transition supersedes
Aborted --> [*]
14.7.11 Screen Rotation¶
ScreenRotationAnimation handles the special case of device rotation. It
captures a screenshot of the pre-rotation state and crossfades/rotates it
into the post-rotation state, coordinating with the display configuration
change.
14.8 Predictive Back Animations¶
14.8.1 Overview¶
Predictive Back (Android 13+) provides real-time back gesture animations
that preview where the user will go before they commit the gesture. The
Shell's back animation system drives these using SurfaceControl
transactions tied to gesture progress.
Source directory:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/
(14 files, ~3,200 lines)
14.8.2 Architecture¶
sequenceDiagram
participant User as User Gesture
participant ISM as InputManager
participant BAC as BackAnimationController
participant Runner as BackAnimationRunner
participant Anim as CrossActivityBackAnimation
participant SF as SurfaceFlinger
User->>ISM: Edge swipe from left/right
ISM->>BAC: onBackMotionEvent(progress)
BAC->>BAC: determine back destination
BAC->>Runner: onBackStarted(BackEvent)
loop gesture in progress
User->>BAC: onBackProgressed(BackEvent)
BAC->>Anim: onBackProgressed(progress, edge)
Anim->>SF: SurfaceControl.Transaction (scale, translate)
end
alt user commits
User->>BAC: onBackInvoked
BAC->>Anim: playCloseAnimation
Anim->>SF: final animation to completion
else user cancels
User->>BAC: onBackCancelled
BAC->>Anim: playCancelAnimation
Anim->>SF: animate back to original state
end
14.8.3 BackAnimationController¶
BackAnimationController is the central coordinator. It receives motion
events from the system's back gesture detector, determines the navigation
target (cross-activity, cross-task, or app callback), and dispatches to the
appropriate animation runner:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
public class BackAnimationController ... {
...
}
14.8.4 Animation Types¶
| Animation Class | When Used |
|---|---|
CrossActivityBackAnimation |
Going back within the same task |
CrossTaskBackAnimation |
Going back to the previous task |
CustomCrossActivityBackAnimation |
Apps providing custom back previews |
DefaultCrossActivityBackAnimation |
Default cross-activity animation |
14.8.5 Gesture-Driven Animation¶
Unlike traditional animations that run on a fixed timeline, predictive back
animations are gesture-driven: their progress is directly tied to the
user's finger position. The BackEvent provides:
progress-- 0.0 (start) to 1.0 (committed)touchX,touchY-- Current finger positionswipeEdge--EDGE_LEFTorEDGE_RIGHT
The animation computes scale, translation, and corner radius as functions
of progress, applying them through SurfaceControl.Transaction each frame.
14.8.6 BackAnimationController State Machine¶
stateDiagram-v2
[*] --> Idle
Idle --> GestureStarted: onBackMotionEvent DOWN
GestureStarted --> Progressing: onBackProgressed
Progressing --> Progressing: onBackProgressed continuous
Progressing --> Committed: onBackInvoked
Progressing --> Cancelled: onBackCancelled
Committed --> PlayingClose: play close animation
Cancelled --> PlayingCancel: play cancel animation
PlayingClose --> TransitionFinished: animation done
PlayingCancel --> Idle: animation done
TransitionFinished --> Idle: cleanup
14.8.7 Progress-to-Transform Mapping¶
The predictive back animations map gesture progress to visual transforms using piecewise functions. For the default cross-activity animation:
| Progress | Scale | Translation X | Corner Radius |
|---|---|---|---|
| 0.0 | 1.0 | 0 | 0 |
| 0.3 | 0.9 | proportional | increasing |
| 0.6 | 0.85 | proportional | increasing |
| 1.0 | 0.8 | max | max |
The animation curves are designed to:
- Start with minimal visual change (low sensitivity near edge)
- Gradually increase the preview effect
- Provide clear visual feedback about the back destination
14.8.8 Back Animation and Shell Transitions Integration¶
When predictive back commits, it triggers a shell transition. The
FLAG_BACK_GESTURE_ANIMATED flag on the TransitionInfo tells the Shell
that this transition was initiated by a back gesture, and the animation
should smoothly continue from the current preview state rather than starting
from scratch.
14.8.9 Back Animation Transform Details¶
The default cross-activity back animation applies these transforms as functions of gesture progress and finger position:
// Simplified transform calculations
// Scale shrinks the departing activity
float scale = lerp(1.0f, 0.9f, progress);
transaction.setScale(leash, scale, scale);
// Translation follows the finger horizontally
float maxTranslation = displayWidth * 0.05f;
float translationX = (swipeEdge == EDGE_LEFT)
? maxTranslation * progress
: -maxTranslation * progress;
transaction.setPosition(leash, translationX, 0);
// Corner radius increases with progress
float cornerRadius = lerp(0, displayCornerRadius, progress);
transaction.setCornerRadius(leash, cornerRadius);
// The entering activity peeks from behind
float enterScale = lerp(0.85f, 1.0f, progress);
transaction.setScale(enterLeash, enterScale, enterScale);
The visual effect is:
- The current activity shrinks slightly and slides in the swipe direction
- Its corners round off to match the display corners
- The previous activity peeks from behind, starting small and growing
14.8.10 ProgressVelocityTracker¶
ProgressVelocityTracker.kt tracks the velocity of the back gesture
progress value. This velocity is used to determine:
- Whether the gesture was a quick fling (should commit immediately)
- The initial velocity for any spring animations during commit/cancel
- Whether to play the commit or cancel animation
14.9 Physics-Based Animations¶
14.9.1 Overview¶
Physics-based animations produce more natural motion by simulating physical
forces (springs, friction) rather than following fixed timing curves.
Unlike ValueAnimator which runs for a fixed duration, physics animations
run until the simulated system reaches equilibrium.
Source directory:
frameworks/base/core/java/com/android/internal/dynamicanimation/animation/
(6 files, ~1,750 lines)
14.9.2 DynamicAnimation Base Class¶
DynamicAnimation is the abstract base for all physics animations. It
registers with AnimationHandler (same as ValueAnimator) for frame
callbacks:
// frameworks/base/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java, lines 43-44
public abstract class DynamicAnimation<T extends DynamicAnimation<T>>
implements AnimationHandler.AnimationFrameCallback {
It provides pre-defined ViewProperty constants for common View properties:
// DynamicAnimation.java, lines 60-70
public static final ViewProperty TRANSLATION_X = new ViewProperty("translationX") {
@Override
public void setValue(View view, float value) {
view.setTranslationX(value);
}
@Override
public Float get(View view) {
return view.getTranslationX();
}
};
Available ViewProperty constants: TRANSLATION_X, TRANSLATION_Y,
TRANSLATION_Z, SCALE_X, SCALE_Y, ROTATION, ROTATION_X,
ROTATION_Y, X, Y, Z, ALPHA, SCROLL_X, SCROLL_Y.
14.9.3 SpringAnimation¶
SpringAnimation drives motion using a SpringForce -- a damped harmonic
oscillator:
// frameworks/base/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java, lines 58-63
public final class SpringAnimation extends DynamicAnimation<SpringAnimation> {
private SpringForce mSpring = null;
private float mPendingPosition = UNSET;
private static final float UNSET = Float.MAX_VALUE;
private boolean mEndRequested = false;
...
}
Usage (from the class Javadoc):
// Create a spring animation targeting view's X property
final SpringAnimation anim = new SpringAnimation(view, DynamicAnimation.X, 0)
.setStartVelocity(5000);
anim.start();
// With custom spring configuration
SpringForce spring = new SpringForce(0)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
final SpringAnimation anim2 = new SpringAnimation(view, DynamicAnimation.SCALE_Y)
.setMinValue(0).setSpring(spring).setStartValue(1);
anim2.start();
14.9.4 SpringForce¶
SpringForce models a damped harmonic oscillator with two key parameters:
// frameworks/base/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java, lines 35-74
public final class SpringForce implements Force {
public static final float STIFFNESS_HIGH = 10_000f;
public static final float STIFFNESS_MEDIUM = 1500f;
public static final float STIFFNESS_LOW = 200f;
public static final float STIFFNESS_VERY_LOW = 50f;
public static final float DAMPING_RATIO_HIGH_BOUNCY = 0.2f;
public static final float DAMPING_RATIO_MEDIUM_BOUNCY = 0.5f;
public static final float DAMPING_RATIO_LOW_BOUNCY = 0.75f;
public static final float DAMPING_RATIO_NO_BOUNCY = 1f;
...
}
The physics simulation uses the damped harmonic oscillator equation:
Where:
k= stiffness (spring constant)c= damping coefficient (derived from damping ratio and natural frequency)m= mass (normalized to 1)
The naturalFreq is sqrt(stiffness), and the solution depends on the
damping ratio:
| Damping Ratio | Behavior | Solution Type |
|---|---|---|
| = 0 | Oscillates forever | Undamped |
| < 1 | Overshoots, oscillates | Under-damped |
| = 1 | Fastest return, no overshoot | Critically damped |
| > 1 | Slow return, no overshoot | Over-damped |
graph LR
subgraph "Spring Damping Behavior"
direction TB
A["Undamped (0.0)"] -.->|"oscillates forever"| X[Position over time]
B["Under-damped (0.2-0.75)"] -.->|"bouncy"| X
C["Critically damped (1.0)"] -.->|"fastest settle"| X
D["Over-damped (>1.0)"] -.->|"slow settle"| X
end
14.9.5 FlingAnimation¶
FlingAnimation simulates a fling gesture with friction. It starts with an
initial velocity and decelerates due to a friction force. The animation ends
when velocity drops below a threshold.
The friction model uses exponential decay:
velocity(t) = initialVelocity * e^(-friction * t)
position(t) = initialPosition + initialVelocity/friction * (1 - e^(-friction * t))
14.9.6 SpringForce Internal Computation¶
The SpringForce class pre-computes intermediate values for efficient per-frame evaluation. The initialization depends on the damping regime:
Under-damped (damping ratio < 1):
dampedFreq = naturalFreq * sqrt(1 - dampingRatio^2)
gammaPlus = -dampingRatio * naturalFreq + dampedFreq * i
gammaMinus = -dampingRatio * naturalFreq - dampedFreq * i
The position and velocity at time t are computed analytically using
the exact solution to the damped harmonic oscillator differential equation.
Critically damped (damping ratio = 1):
position(t) = (c1 + c2 * t) * e^(-naturalFreq * t)
velocity(t) = (c2 - naturalFreq * (c1 + c2 * t)) * e^(-naturalFreq * t)
Over-damped (damping ratio > 1):
gammaPlus = -dampingRatio * naturalFreq + naturalFreq * sqrt(dampingRatio^2 - 1)
gammaMinus = -dampingRatio * naturalFreq - naturalFreq * sqrt(dampingRatio^2 - 1)
The animation checks for convergence each frame by comparing both position and velocity against thresholds:
valueThreshold = based on the minimum visible change
velocityThreshold = valueThreshold * VELOCITY_THRESHOLD_MULTIPLIER (62.5)
The VELOCITY_THRESHOLD_MULTIPLIER (1000.0 / 16.0 = 62.5) means that if
it would take more than one frame (16ms) to move by the value threshold at
the current velocity, the spring is considered at rest.
14.9.7 DynamicAnimation Lifecycle¶
stateDiagram-v2
[*] --> Created
Created --> Running: start
Running --> Running: doAnimationFrame each VSYNC
Running --> Ended: force reaches equilibrium
Running --> Cancelled: cancel
Ended --> [*]
Cancelled --> [*]
note right of Running
Each frame:
1. Get delta time
2. Ask Force for new value/velocity
3. Check min/max bounds
4. Update property on target
5. Check if at rest
end note
14.9.8 FlingAnimation Detailed Behavior¶
FlingAnimation uses an exponential decay model with configurable friction:
position(t) = startPosition + startVelocity / friction * (1 - e^(-friction * t))
velocity(t) = startVelocity * e^(-friction * t)
The friction coefficient determines how quickly the fling decelerates:
| Friction Value | Behavior | Typical Use |
|---|---|---|
| 0.1 | Very slippery, long fling | Ice-like surfaces |
| 0.5 | Low friction | Smooth scrolling lists |
| 1.0 | Default | Standard fling |
| 1.5 | Moderate friction | Slower deceleration |
| 3.0 | High friction | Quick stop |
| 10.0 | Very high friction | Near-instant stop |
The animation stops when velocity drops below the minimum visible change threshold (approximately 1 pixel/second for position properties).
FlingAnimation also supports min/max bounds. When the value hits a bound,
the animation stops immediately (no bounce). To add bounce behavior,
chain a SpringAnimation when the fling ends at a boundary:
FlingAnimation fling = new FlingAnimation(view, DynamicAnimation.TRANSLATION_X);
fling.setMinValue(0f);
fling.setMaxValue(maxX);
fling.addEndListener((anim, canceled, value, velocity) -> {
// If ended at a boundary with remaining velocity, spring back
if (value <= 0 || value >= maxX) {
float target = Math.max(0, Math.min(value, maxX));
new SpringAnimation(view, DynamicAnimation.TRANSLATION_X, target)
.setStartVelocity(velocity)
.start();
}
});
14.9.9 DynamicAnimation ViewProperty Architecture¶
The ViewProperty abstract class provides a type-safe, no-reflection
mechanism for animating View properties:
classDiagram
class FloatProperty~View~ {
<<abstract>>
+setValue(View, float)*
+get(View) Float*
}
class ViewProperty {
<<abstract>>
}
class TRANSLATION_X
class TRANSLATION_Y
class TRANSLATION_Z
class SCALE_X
class SCALE_Y
class ROTATION
class ROTATION_X
class ROTATION_Y
class ALPHA
class X
class Y
class Z
class SCROLL_X
class SCROLL_Y
FloatProperty <|-- ViewProperty
ViewProperty <|-- TRANSLATION_X
ViewProperty <|-- TRANSLATION_Y
ViewProperty <|-- TRANSLATION_Z
ViewProperty <|-- SCALE_X
ViewProperty <|-- SCALE_Y
ViewProperty <|-- ROTATION
ViewProperty <|-- ROTATION_X
ViewProperty <|-- ROTATION_Y
ViewProperty <|-- ALPHA
ViewProperty <|-- X
ViewProperty <|-- Y
ViewProperty <|-- Z
ViewProperty <|-- SCROLL_X
ViewProperty <|-- SCROLL_Y
Each property directly calls the corresponding View setter method, avoiding reflection overhead:
public static final ViewProperty ALPHA = new ViewProperty("alpha") {
@Override
public void setValue(View view, float value) {
view.setAlpha(value);
}
@Override
public Float get(View view) {
return view.getAlpha();
}
};
14.9.10 Force Interface¶
The Force interface abstracts the physics model, enabling custom force
implementations:
public interface Force {
/**
* Returns the acceleration at the given position and velocity.
* @param position current position
* @param velocity current velocity
* @return acceleration
*/
float getAcceleration(float position, float velocity);
/**
* Returns whether the animation is at equilibrium.
* @param value current value
* @param velocity current velocity
* @return true if at rest
*/
boolean isAtEquilibrium(float value, float velocity);
}
SpringForce implements this interface to provide spring dynamics.
Developers can implement custom forces (e.g., gravity, magnetic attraction)
by implementing this interface and using it with DynamicAnimation.
14.9.11 Scroller and OverScroller Physics¶
While not part of the DynamicAnimation package, Scroller and
OverScroller implement the fling physics used by all standard scrollable
views.
OverScroller extends Scroller with elastic overscroll behavior at
edges. The overscroll effect uses a spring-like model where the
displacement is proportional to the scroll velocity at the edge:
graph LR
subgraph "OverScroller States"
SCROLL[Scrolling] -->|reach edge with velocity| OVER[Overscroll]
OVER -->|spring back| SCROLL
SCROLL -->|fling| FLING[Flinging]
FLING -->|reach edge| OVER
FLING -->|decelerate to stop| SCROLL
end
The fling deceleration model uses a spline-based interpolation that approximates physical friction more accurately than pure exponential decay.
14.9.12 Physics Animation Integration with Shell¶
The Shell process uses physics animations extensively for interactive animations. Bubble animations, for example, use spring dynamics to make bubbles feel physically connected to the user's finger.
14.9.13 Scroller and OverScroller¶
While not part of the DynamicAnimation package, Scroller and
OverScroller in android.widget provide physics-based scrolling models:
Scroller-- Basic fling with decelerationOverScroller-- Adds elastic overscroll at boundaries
These are used by ScrollView, ListView, RecyclerView, and other
scrollable containers for their fling behavior.
14.10 Native HWUI Animation¶
14.10.1 Overview¶
HWUI (Hardware UI) provides native C++ animation support that runs on the RenderThread, completely independent of the UI thread. This means animations continue smoothly even if the UI thread is blocked (e.g., during garbage collection or heavy layout).
Source files in frameworks/base/libs/hwui/:
| File | Lines | Purpose |
|---|---|---|
Animator.cpp |
~460 | Base animation engine |
Animator.h |
~280 | Animation class declarations |
AnimatorManager.cpp |
~207 | Per-RenderNode animation management |
AnimatorManager.h |
~80 | Manager declarations |
Interpolator.cpp |
~160 | Native interpolator implementations |
AnimationContext.cpp |
~100 | Frame timing context |
PropertyValuesAnimatorSet.cpp |
~200 | Multi-property animation set |
14.10.2 BaseRenderNodeAnimator¶
The core native animation class manages a state machine that synchronizes between UI thread (staging) and RenderThread (actual animation):
// frameworks/base/libs/hwui/Animator.cpp, lines 34-47
BaseRenderNodeAnimator::BaseRenderNodeAnimator(float finalValue)
: mTarget(nullptr)
, mStagingTarget(nullptr)
, mFinalValue(finalValue)
, mDeltaValue(0)
, mFromValue(0)
, mStagingPlayState(PlayState::NotStarted)
, mPlayState(PlayState::NotStarted)
, mHasStartValue(false)
, mStartTime(0)
, mDuration(300)
, mStartDelay(0)
, mMayRunAsync(true)
, mPlayTime(0) {}
14.10.3 Staging Pattern¶
HWUI animations use a staging pattern to safely transfer animation state from the UI thread to the RenderThread:
sequenceDiagram
participant UI as UI Thread
participant RT as RenderThread
UI->>UI: animator.start()
Note over UI: mStagingPlayState = Running
Note over UI: mStagingRequests.push(Start)
UI->>RT: syncFrameState (next frame)
RT->>RT: pushStaging()
Note over RT: resolve staging requests
Note over RT: mPlayState = Running
loop each RenderThread frame
RT->>RT: animate(context)
Note over RT: compute fraction from time
Note over RT: apply interpolator
Note over RT: update RenderNode property
end
14.10.4 PlayState Machine¶
// Animator.cpp, lines 118-151 (resolveStagingRequest)
switch (request) {
case Request::Start:
mPlayState = PlayState::Running;
break;
case Request::Reverse:
mPlayState = PlayState::Reversing;
break;
case Request::Reset:
mPlayTime = 0;
mPlayState = PlayState::Finished;
mPendingActionUponFinish = Action::Reset;
break;
case Request::Cancel:
mPlayState = PlayState::Finished;
break;
case Request::End:
mPlayTime = mPlayState == PlayState::Reversing ? 0 : mDuration;
mPlayState = PlayState::Finished;
mPendingActionUponFinish = Action::End;
break;
}
stateDiagram-v2
[*] --> NotStarted
NotStarted --> Running: Start request
NotStarted --> Reversing: Reverse request
Running --> Finished: duration elapsed / Cancel / End
Running --> Reversing: Reverse request
Reversing --> Finished: play time reaches 0 / Cancel / End
Reversing --> Running: Start request
Finished --> [*]
Finished --> Running: Reset + Start
14.10.5 AnimatorManager¶
AnimatorManager (207 lines) manages all animations attached to a single
RenderNode:
// frameworks/base/libs/hwui/AnimatorManager.cpp, lines 34-55
AnimatorManager::AnimatorManager(RenderNode& parent)
: mParent(parent), mAnimationHandle(nullptr), mCancelAllAnimators(false) {}
void AnimatorManager::addAnimator(const sp<BaseRenderNodeAnimator>& animator) {
RenderNode* stagingTarget = animator->stagingTarget();
if (stagingTarget == &mParent) return;
mNewAnimators.emplace_back(animator.get());
if (stagingTarget) {
stagingTarget->removeAnimator(animator);
}
animator->attach(&mParent);
}
The pushStaging() method transfers new animators from the staging list
to the active list, and animate() advances all active animators for the
current frame.
14.10.6 Java-Side JNI Bridge¶
On the Java side, RenderNodeAnimator (approximately 513 lines) wraps native
HWUI animators. View property animations (translationX, alpha, etc.) that
target a RenderNode property use this path for maximum performance:
// When you call view.animate().translationX(100):
// 1. ViewPropertyAnimator creates a RenderNodeAnimator
// 2. RenderNodeAnimator calls nStart() via JNI
// 3. Native BaseRenderNodeAnimator starts on RenderThread
// 4. Each RenderThread frame: native animate() updates RenderNode
// 5. No UI thread involvement after start!
14.10.7 HWUI animate() Core Loop¶
The animate() method on BaseRenderNodeAnimator computes the current
value each RenderThread frame:
flowchart TD
A[RenderThread frame] --> B[AnimatorManager.animate]
B --> C{For each active animator}
C --> D[Compute playTime from currentFrameTime - startTime]
D --> E{playTime >= startDelay?}
E -->|No| F[Still in delay, skip]
E -->|Yes| G[fraction = playTime / duration]
G --> H[Clamp fraction to 0..1]
H --> I[interpolatedFraction = interpolator.interpolate fraction]
I --> J[value = fromValue + deltaValue * interpolatedFraction]
J --> K[Update RenderNode property]
K --> L{fraction >= 1.0?}
L -->|Yes| M[Mark finished, schedule callback to UI thread]
L -->|No| N[Continue next frame]
14.10.8 Property Types in HWUI¶
HWUI can animate these RenderNode properties natively:
| Property | Type | Description |
|---|---|---|
TRANSLATION_X |
float | Horizontal translation |
TRANSLATION_Y |
float | Vertical translation |
TRANSLATION_Z |
float | Z-axis translation (elevation) |
SCALE_X |
float | Horizontal scale |
SCALE_Y |
float | Vertical scale |
ROTATION |
float | Z-axis rotation |
ROTATION_X |
float | X-axis rotation (3D) |
ROTATION_Y |
float | Y-axis rotation (3D) |
ALPHA |
float | Opacity |
X |
float | Absolute X position |
Y |
float | Absolute Y position |
Z |
float | Absolute Z position |
These map directly to RenderNode properties and are applied during the display list replay phase without any Java callback.
14.10.9 HWUI Interpolator Implementation¶
The native interpolator infrastructure mirrors Java exactly. In
frameworks/base/libs/hwui/Interpolator.cpp:
| Native Interpolator | Java Equivalent | Formula |
|---|---|---|
AccelerateDecelerateInterpolator |
Same | cos((t+1)*PI)/2 + 0.5 |
AccelerateInterpolator |
Same | t^(2*factor) |
DecelerateInterpolator |
Same | 1-(1-t)^(2*factor) |
LinearInterpolator |
Same | t |
PathInterpolator |
Same | Binary search on path points |
OvershootInterpolator |
Same | Cubic overshoot |
AnticipateInterpolator |
Same | Anticipation curve |
BounceInterpolator |
Same | Piecewise bounce |
CycleInterpolator |
Same | sin(2*PI*cycles*t) |
LUTInterpolator |
N/A | Lookup table from Java samples |
The LUTInterpolator is a special native interpolator used when a Java
interpolator does not have a native equivalent. The Java interpolator is
sampled at regular intervals during pushStaging(), and the resulting
lookup table is used for RenderThread animation.
14.10.10 PropertyValuesAnimatorSet (Native)¶
For AnimatedVectorDrawable, the native PropertyValuesAnimatorSet
(frameworks/base/libs/hwui/PropertyValuesAnimatorSet.cpp) provides a
complete AnimatorSet implementation in C++ that runs on the RenderThread.
This enables complex multi-property AVD animations to run without any
Java callbacks.
14.10.11 AnimationContext and Frame Timing¶
AnimationContext provides the frame timing context for native animations:
// AnimationContext provides frameTimeMs() used by animations
// to calculate elapsed time and fraction
class AnimationContext {
nsecs_t frameTimeMs();
void startFrame();
void runRemainingAnimations(TreeInfo& info);
...
};
The frame time comes from the RenderThread's VSYNC timestamp, which may differ slightly from the UI thread's Choreographer timestamp. This is intentional -- RenderThread processes the frame after the UI thread has finished, so it uses a slightly later timestamp.
14.10.12 HWUI Animation and Display Lists¶
HWUI animations modify RenderNode properties, which are applied during
display list replay. The modification happens in-place without
re-recording the display list, making property animations extremely
efficient:
graph TD
subgraph "UI Thread"
DL[Record Display List] --> |"only on layout/draw"| SYNC[Sync to RenderThread]
end
subgraph "RenderThread"
SYNC --> PS[pushStaging - transfer new animators]
PS --> AN[animate - update RenderNode properties]
AN --> DRAW[Draw display list with updated properties]
DRAW --> |"properties applied during replay"| GPU[GPU render]
end
style AN fill:#f96,stroke:#333,stroke-width:2px
Because animations modify properties but not the display list structure,
the RenderThread can animate smoothly even if the UI thread never runs.
This is why view.animate().alpha(0.5f) continues smoothly during GC
pauses, while a custom ValueAnimator that calls invalidate() would
stutter.
14.10.13 HWUI vs Java Animation Performance¶
| Aspect | Java (ValueAnimator) | Native (HWUI) |
|---|---|---|
| Thread | UI Thread | RenderThread |
| Survives UI jank | No | Yes |
| Property types | Any Java property | RenderNode properties only |
| Flexibility | High (custom evaluators) | Limited (float properties) |
| Overhead | Reflection, boxing | Direct native property set |
| Use case | Complex, multi-object | Simple view property animations |
14.11 Drawable and Vector Animations¶
14.11.1 AnimatedVectorDrawable¶
AnimatedVectorDrawable (approximately 1,870 lines) animates the
individual properties of a VectorDrawable -- paths, groups, and fills.
Starting from API 25, it runs on the RenderThread for jank-free
performance:
// frameworks/base/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java, lines 71-80
/**
* Starting from API 25, AnimatedVectorDrawable runs on RenderThread (as
* opposed to on UI thread for earlier APIs). This means animations in
* AnimatedVectorDrawable can remain smooth even when there is heavy workload
* on the UI thread.
*/
14.11.2 AVD Architecture¶
graph TD
subgraph "XML Resources"
AVD["animated-vector XML"] --> VD["VectorDrawable XML"]
AVD --> OA1["ObjectAnimator XML (path)"]
AVD --> OA2["ObjectAnimator XML (group)"]
end
subgraph "Runtime"
AVD2[AnimatedVectorDrawable] --> VDS[VectorDrawableState]
AVD2 --> AS[AnimatorSet]
AS --> OA3[ObjectAnimator - pathData]
AS --> OA4[ObjectAnimator - fillColor]
AS --> OA5[ObjectAnimator - rotation]
end
subgraph "Rendering"
AVD2 --> |API 25+| RT[RenderThread native animator]
AVD2 --> |API < 25| UI[UI Thread animator]
RT --> Canvas[RecordingCanvas]
UI --> Canvas
end
14.11.3 VectorDrawable Properties¶
VectorDrawable (approximately 2,398 lines) exposes numerous animatable
properties:
| Property | Target | Description |
|---|---|---|
pathData |
Path | SVG path morphing |
fillColor |
Path | Fill color |
fillAlpha |
Path | Fill opacity |
strokeColor |
Path | Stroke color |
strokeAlpha |
Path | Stroke opacity |
strokeWidth |
Path | Stroke width |
trimPathStart |
Path | Trim start (0-1) |
trimPathEnd |
Path | Trim end (0-1) |
trimPathOffset |
Path | Trim offset |
rotation |
Group | Group rotation |
pivotX, pivotY |
Group | Rotation pivot |
scaleX, scaleY |
Group | Group scale |
translateX, translateY |
Group | Group translation |
14.11.4 AVD RenderThread Execution Path¶
Starting from API 25, AVD animations execute natively on the RenderThread through this path:
sequenceDiagram
participant App as Application Code
participant AVD as AnimatedVectorDrawable
participant AVDS as AnimatorSet (native)
participant RT as RenderThread
participant RN as RenderNode
participant VD as VectorDrawable (native)
App->>AVD: avd.start()
AVD->>AVDS: Start native AnimatorSet
AVDS->>RT: Register with RenderThread frame callback
loop each RenderThread frame
RT->>AVDS: onAnimationFrame(frameTime)
AVDS->>AVDS: Compute interpolated values
AVDS->>VD: Update path data / colors / transforms
VD->>RN: Invalidate RenderNode
RN->>RT: Re-record display list
RT->>RT: Draw frame
end
AVDS->>AVD: Animation complete callback
AVD->>App: AnimationCallback.onAnimationEnd()
The key advantage is that the entire animation loop -- value computation, property update, and drawing -- happens on the RenderThread without any Java/JNI overhead per frame.
14.11.5 Path Morphing in AVD¶
One of the most powerful AVD features is path morphing -- smoothly transitioning between two SVG path shapes. This requires:
- Both paths must have the same number and types of path commands
- The framework interpolates each control point independently
- The result is a smooth morph between shapes
<objectAnimator
android:propertyName="pathData"
android:valueFrom="M0,0 L24,0 L24,24 L0,24 Z"
android:valueTo="M12,0 L24,12 L12,24 L0,12 Z"
android:valueType="pathType"
android:duration="500"/>
This morphs a square into a diamond. The framework uses PathParser to
decompose each path into a sequence of points, then linearly interpolates
each point between the start and end positions.
14.11.6 Trim Path Animation¶
The trim path properties (trimPathStart, trimPathEnd, trimPathOffset)
enable "drawing" effects where a path appears to be drawn progressively:
<!-- Animate trimPathEnd from 0 to 1 to "draw" the path -->
<objectAnimator
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:duration="1000"/>
Combined with trimPathOffset, this can create circular loading spinners
and progress indicators.
14.11.7 AVD Performance Characteristics¶
| Aspect | API < 25 | API >= 25 |
|---|---|---|
| Thread | UI Thread | RenderThread |
| Path morphing | Per-frame JNI | Pure native |
| Multiple AVDs | Each adds UI load | Independent of UI |
| During GC | Stutters | Smooth |
| During layout | Stutters | Smooth |
| Complexity limit | ~100 path nodes | ~500 path nodes |
Best practices for AVD performance:
- Keep path complexity low (fewer path commands = less computation)
- Prefer transforms (rotation, scale, translation) over path morphing
- Use trim path for "drawing" effects instead of path morphing
- Pre-compose complex shapes in a vector editor rather than animating many simple shapes
14.11.8 VectorDrawable Rendering Pipeline¶
flowchart TD
A[XML/Code defines VectorDrawable] --> B[Parse groups, paths, clips]
B --> C[Build native VectorDrawable tree]
C --> D{Animation running?}
D -->|No| E[Static render to Canvas]
D -->|Yes| F[AnimatedVectorDrawableState]
F --> G[Native PropertyValuesAnimatorSet]
G --> |each frame| H[Update native properties]
H --> I[Invalidate RenderNode]
I --> J[RenderThread redraws VD to texture]
J --> K[Composite with rest of UI]
14.11.9 AnimationDrawable¶
AnimationDrawable provides simple frame-by-frame animation, displaying
a sequence of drawables at fixed intervals. Each frame is specified as a
drawable with a duration in the XML:
<animation-list android:oneshot="false">
<item android:drawable="@drawable/frame1" android:duration="100"/>
<item android:drawable="@drawable/frame2" android:duration="100"/>
<item android:drawable="@drawable/frame3" android:duration="100"/>
</animation-list>
14.11.10 AnimatedImageDrawable¶
AnimatedImageDrawable (API 28+) supports animated image formats like
GIF and WebP. It decodes frames on a worker thread and uses Choreographer
for frame scheduling, providing smooth playback without blocking the UI
thread.
14.12 Choreographer¶
14.12.1 Overview¶
Choreographer (1,714 lines) is the central timing coordinator for all
UI-thread work in Android. It receives VSYNC signals from the display
subsystem and dispatches ordered callbacks that collectively produce each
frame.
Source:
frameworks/base/core/java/android/view/Choreographer.java
14.12.2 Callback Types and Ordering¶
// Choreographer.java, lines 303-355
CALLBACK_INPUT = 0 // Input event processing
CALLBACK_ANIMATION = 1 // Animation frame callbacks
CALLBACK_INSETS_ANIMATION = 2 // WindowInsetsAnimation updates
CALLBACK_TRAVERSAL = 3 // View measure/layout/draw
CALLBACK_COMMIT = 4 // Post-draw commit
graph LR
V[VSYNC Signal] --> I[INPUT]
I --> A[ANIMATION]
A --> IA[INSETS_ANIMATION]
IA --> T[TRAVERSAL]
T --> C[COMMIT]
C --> |next VSYNC| V
The ordering ensures:
- Input events are processed first (finger positions updated)
- Animations run next (properties updated based on new time)
- Inset animations gather combined inset updates
- Traversal performs layout and draw with the new state
- Commit adjusts start times if frames were skipped
14.12.3 Per-Thread Singleton¶
Each Looper thread gets its own Choreographer via ThreadLocal:
// Choreographer.java, lines 127-141
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
if (looper == Looper.getMainLooper()) {
mMainInstance = choreographer;
}
return choreographer;
}
};
14.12.4 VSYNC Integration¶
Choreographer receives VSYNC through FrameDisplayEventReceiver:
// Choreographer.java, lines 361-376
private Choreographer(Looper looper, int vsyncSource, long layerHandle) {
mLooper = looper;
mHandler = new FrameHandler(looper);
mDisplayEventReceiver = USE_VSYNC
? new FrameDisplayEventReceiver(looper, vsyncSource, layerHandle)
: null;
mLastFrameTimeNanos = Long.MIN_VALUE;
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
...
}
14.12.5 Frame Callback Scheduling¶
sequenceDiagram
participant App
participant Choreo as Choreographer
participant DEV as DisplayEventReceiver
participant HW as Display Hardware
App->>Choreo: postFrameCallback(callback)
Choreo->>Choreo: addCallbackLocked(ANIMATION, callback)
Choreo->>DEV: scheduleVsync()
HW->>DEV: VSYNC signal
DEV->>Choreo: onVsync(timestampNanos, frameIntervalNanos)
Choreo->>Choreo: doFrame(frameTimeNanos)
loop for each callback type (0..4)
Choreo->>Choreo: doCallbacks(callbackType, frameTimeNanos)
end
14.12.6 Callback Queue¶
Each callback type has its own CallbackQueue (a singly-linked list sorted
by due time):
// Choreographer.java, postCallbackDelayedInternal (lines 612-634)
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
14.12.7 Frame Time and Jank Detection¶
Choreographer detects skipped frames and logs warnings:
// Choreographer.java, line 178-179
private static final int SKIPPED_FRAME_WARNING_LIMIT = SystemProperties.getInt(
"debug.choreographer.skipwarning", 30);
The famous log message "Skipped N frames! The application may be doing
too much work on its main thread" originates from the doFrame() method
when the time gap between frames exceeds SKIPPED_FRAME_WARNING_LIMIT *
frameInterval.
14.12.8 Buffer Stuffing Recovery¶
Modern Choreographer includes buffer stuffing detection and recovery (lines 236-283). When the app is blocked waiting for buffer release (indicating too many queued frames), Choreographer adds timing offsets to recover:
// Choreographer.java, lines 280-283
public void onWaitForBufferRelease(long durationNanos) {
if (durationNanos > mLastFrameIntervalNanos / 2) {
mBufferStuffingState.isStuffed.set(true);
}
}
14.12.9 FrameInfo for Jank Tracking¶
FrameInfo records timestamps at key points during frame processing,
used by the jank tracking infrastructure (Perfetto, HWUI) to measure
where time is spent in each frame.
14.12.10 The doFrame() Method¶
The core frame dispatch method processes all callback types in order:
sequenceDiagram
participant DEV as DisplayEventReceiver
participant FH as FrameHandler
participant Choreo as Choreographer
DEV->>FH: MSG_DO_FRAME
FH->>Choreo: doFrame(frameTimeNanos)
Note over Choreo: Check for skipped frames
Note over Choreo: Log warning if > 30 frames skipped
Choreo->>Choreo: mFrameInfo.markInputHandlingStart()
Choreo->>Choreo: doCallbacks(CALLBACK_INPUT)
Choreo->>Choreo: mFrameInfo.markAnimationsStart()
Choreo->>Choreo: doCallbacks(CALLBACK_ANIMATION)
Choreo->>Choreo: mFrameInfo.markInsetAnimationsStart()
Choreo->>Choreo: doCallbacks(CALLBACK_INSETS_ANIMATION)
Choreo->>Choreo: mFrameInfo.markPerformTraversalsStart()
Choreo->>Choreo: doCallbacks(CALLBACK_TRAVERSAL)
Choreo->>Choreo: doCallbacks(CALLBACK_COMMIT)
Each doCallbacks() call extracts all callbacks from the queue whose due
time has passed and invokes them.
14.12.11 VSYNC Source Types¶
Choreographer supports two VSYNC sources:
| Source | Constant | Usage |
|---|---|---|
| App VSYNC | VSYNC_SOURCE_APP |
UI rendering (default) |
| SF VSYNC | VSYNC_SOURCE_SURFACE_FLINGER |
Compositor-timed operations |
App VSYNC fires slightly earlier than SF VSYNC to give the app time to
render before SurfaceFlinger composites. The SurfaceAnimationRunner uses
SF VSYNC via SfVsyncFrameCallbackProvider to synchronize WM animations
with the compositor.
14.12.12 Frame Scheduling¶
When a callback is posted, Choreographer schedules the next VSYNC if one is not already scheduled:
// Choreographer.java, scheduleFrameLocked (simplified)
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
// Post message to schedule VSYNC on the correct thread
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// Fallback: use delayed message
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
Key detail: messages are set as asynchronous to bypass any synchronization barriers on the message queue, ensuring VSYNC processing is never delayed by other messages.
14.12.13 FrameDisplayEventReceiver¶
The FrameDisplayEventReceiver is a private inner class that bridges
between the native display event system and the Java Choreographer:
private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
@Override
public void onVsync(long timestampNanos, long physicalDisplayId,
int frame, VsyncEventData vsyncEventData) {
...
mTimestampNanos = timestampNanos;
mFrame = frame;
mLastVsyncEventData = vsyncEventData;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
}
}
The receiver is a Runnable that posts itself as a message. The message
timestamp matches the VSYNC timestamp, ensuring the frame processing
happens at the correct time relative to other messages in the queue.
14.12.14 FPS Divisor¶
For low-FPS experiments, Choreographer supports an FPS divisor:
When mFPSDivisor > 1, Choreographer skips frames by not processing
every VSYNC. For example, mFPSDivisor = 2 on a 120Hz display would
result in 60fps rendering.
14.12.15 Choreographer System Properties¶
| Property | Default | Purpose |
|---|---|---|
debug.choreographer.vsync |
true | Enable VSYNC-based timing |
debug.choreographer.frametime |
true | Use frame time instead of current time |
debug.choreographer.skipwarning |
30 | Number of skipped frames before warning |
14.12.16 VsyncCallback vs FrameCallback¶
Choreographer offers two callback interfaces:
// Traditional callback - receives only frame time
public interface FrameCallback {
void doFrame(long frameTimeNanos);
}
// Enhanced callback - receives full VSYNC event data
public interface VsyncCallback {
void onVsync(FrameData data);
}
VsyncCallback (API 33+) provides richer information including the
VSYNC ID, preferred frame timeline, and expected presentation time.
This enables more precise animation timing for variable refresh rate
displays.
14.12.17 Expected Presentation Time¶
On devices with variable refresh rate displays, the presentation time may
not be a fixed interval from the VSYNC. Choreographer exposes the expected
presentation time through FrameData:
public static class FrameData {
public long getFrameTimeNanos();
public long getPreferredFrameTimelineDeadlineNanos();
public long getPreferredFrameTimelinePresentationNanos();
public long getPreferredFrameTimelineVsyncId();
...
}
Animations can use the expected presentation time to pre-compute the value that will be visible when the frame actually appears on screen, rather than the value at the animation callback time.
14.12.18 Choreographer and AnimationHandler Integration¶
graph TD
C[Choreographer] -->|CALLBACK_ANIMATION| AH[AnimationHandler.mFrameCallback]
AH -->|doAnimationFrame| VA1[ValueAnimator 1]
AH -->|doAnimationFrame| VA2[ValueAnimator 2]
AH -->|doAnimationFrame| SA1[SpringAnimation 1]
AH -->|doAnimationFrame| FA1[FlingAnimation 1]
C -->|CALLBACK_ANIMATION| FC1[FrameCallback 1 - app registered]
C -->|CALLBACK_ANIMATION| FC2[FrameCallback 2 - app registered]
C -->|CALLBACK_TRAVERSAL| VRI[ViewRootImpl.doTraversal]
14.13 Specialized Shell Animations¶
14.13.1 Shell Animation Infrastructure¶
The Shell process manages all system-level animations through a consistent infrastructure. Each subsystem (PIP, unfold, back, desktop) provides animation handlers that integrate with the Shell's main thread and transaction pipeline:
graph TD
subgraph "Shell Animation Infrastructure"
ME[Shell Main Executor] --> TC[TransactionPool]
TC --> TXN[SurfaceControl.Transaction]
TXN --> SF[SurfaceFlinger]
ME --> DTH[DefaultTransitionHandler]
ME --> PIP[PipTransitionHandler]
ME --> BAC[BackAnimationController]
ME --> UF[UnfoldTransitionHandler]
ME --> DM[DesktopModeTransitionHandler]
end
subgraph "Common Utilities"
TAH[TransitionAnimationHelper]
DSA[DefaultSurfaceAnimator]
WT[WindowThumbnail]
end
DTH --> TAH
DTH --> DSA
DTH --> WT
All shell animations share:
- TransactionPool: Reusable transaction objects to avoid allocation
- ValueAnimator: Standard property animation for timing
- SurfaceControl operations: Direct compositor-level transforms
- Jank monitoring: Integration with InteractionJankMonitor
14.13.2 Picture-in-Picture (PIP) Animations¶
The PIP animation system handles the unique requirements of transitioning a window into and out of the PIP overlay:
Source: frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/
Key animations:
- Enter PIP: Full-screen window shrinks to PIP bounds with corner radius
- Exit PIP: PIP window expands back to full screen
- PIP resize: Smooth bounds change while in PIP mode
- PIP dismiss: Fade + scale down to dismiss point
The animations operate directly on SurfaceControl transactions for
smooth, compositor-level performance.
14.13.3 Unfold Animations¶
Source: frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/
Unfold animations handle the foldable device transitions:
- Unfold: Content scales and translates as the device opens
- Fold: Reverse of unfold
- Half-fold: Content adjusts for tabletop mode
These use SurfaceControl transforms driven by the hinge angle sensor,
providing real-time visual feedback as the user opens or closes the device.
14.13.4 Desktop Mode Animations¶
Desktop mode (freeform windowing) introduces window management animations:
- Window drag and resize with spring-based snapping
- Window minimize/maximize transitions
- Window tiling animations
14.13.5 Letterbox Animations¶
When an app that does not support the current display aspect ratio is shown, the system applies letterbox bars and may animate the transition between different letterbox states.
14.13.6 Dimmer Animations¶
DimmerAnimationHelper in the WM provides smooth dimming transitions
when a window needs a dim layer behind it (e.g., dialogs, split-screen
dividers).
14.13.7 Split-Screen Divider Animations¶
When entering or exiting split-screen mode, the divider bar animates between its hidden and visible states. The animation uses spring physics for the divider position and smooth alpha transitions for visibility.
14.13.8 Letterbox Animation Details¶
Letterbox animations handle the transition between different letterbox states:
| State Transition | Animation |
|---|---|
| No letterbox -> Letterboxed | Bars slide in from edges |
| Letterboxed -> No letterbox | Bars slide out to edges |
| Letterbox position change | Smooth bounds transition |
| Orientation change with letterbox | Crossfade with new configuration |
14.13.9 App Launch Animation¶
The default app launch animation in Shell typically follows this sequence:
sequenceDiagram
participant L as Launcher Surface
participant A as App Surface
participant BG as Background
Note over L,BG: Transition starts
L->>L: Alpha: 1.0 -> 0.0 (fade out)
A->>A: Scale: 0.8 -> 1.0 (scale up)
A->>A: Alpha: 0.0 -> 1.0 (fade in)
A->>A: CornerRadius: large -> 0 (square off)
Note over L,BG: Transition ends
The animation is customizable through window animation style attributes
in the app's theme. Custom launchers can provide their own animations
through RemoteTransition.
14.13.10 Task-to-Task Animation¶
When switching between tasks (e.g., from Recents), the animation handles:
- Closing task: Slides out or fades with scale-down
- Opening task: Slides in or fades with scale-up
- Wallpaper: Parallax effect if visible
- Navigation bar: Fade between app-colored and default states
14.13.11 Recents Animation Integration¶
The Recents animation (swipe-up gesture) is a special case that gives the Launcher temporary control of the entire surface hierarchy:
sequenceDiagram
participant User as User Gesture
participant SS as System Server
participant Launcher as Launcher App
User->>SS: Swipe-up gesture detected
SS->>Launcher: onAnimationStart(RemoteAnimationTarget[])
Launcher->>Launcher: Create and run custom animation
loop gesture in progress
User->>Launcher: onMotionEvent
Launcher->>Launcher: Update surface positions/scales
Launcher->>SS: SurfaceControl.Transaction
end
alt user releases to Recents
Launcher->>Launcher: Animate to Recents overview
else user releases to Home
Launcher->>SS: finishRecentsAnimation(toHome)
else user releases to app
Launcher->>SS: finishRecentsAnimation(toApp)
end
This gives the Launcher full creative control over the transition animation, enabling custom Recents UI designs.
14.13.12 Animation Synchronization with SurfaceFlinger¶
All shell animations ultimately produce SurfaceControl.Transaction
objects that are applied atomically by SurfaceFlinger. Key transaction
operations used:
| Operation | Purpose |
|---|---|
setPosition(x, y) |
Move the surface |
setScale(sx, sy) |
Scale the surface |
setAlpha(alpha) |
Set surface opacity |
setMatrix(a, b, c, d) |
Apply 2x2 transform matrix |
setCornerRadius(r) |
Round corners |
setBackgroundBlurRadius(r) |
Apply background blur |
setCrop(rect) |
Clip to rectangle |
setLayer(z) |
Set Z-order |
setRelativeLayer(ref, z) |
Z-order relative to another surface |
reparent(newParent) |
Move in the surface hierarchy |
show() / hide() |
Visibility |
Transactions can be applied synchronously (apply()) or deferred to the
next VSYNC (setDesiredPresentTime()) for smoother timing.
14.14 Try It¶
14.14.1 Property Animation: Bouncing Ball¶
Create a simple property animation that bounces a view:
// In an Activity
View ball = findViewById(R.id.ball);
// Method 1: ValueAnimator with manual update
ValueAnimator animator = ValueAnimator.ofFloat(0f, 500f);
animator.setDuration(1000);
animator.setInterpolator(new BounceInterpolator());
animator.addUpdateListener(animation -> {
float value = (float) animation.getAnimatedValue();
ball.setTranslationY(value);
});
animator.start();
// Method 2: ObjectAnimator (preferred)
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(
ball, View.TRANSLATION_Y, 0f, 500f);
objectAnimator.setDuration(1000);
objectAnimator.setInterpolator(new BounceInterpolator());
objectAnimator.start();
// Method 3: ViewPropertyAnimator (most concise)
ball.animate()
.translationY(500f)
.setDuration(1000)
.setInterpolator(new BounceInterpolator())
.start();
14.14.2 Shared Element Activity Transition¶
In the calling Activity:
// Define shared element
ImageView imageView = findViewById(R.id.shared_image);
imageView.setTransitionName("hero_image");
// Launch with shared element
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
this, imageView, "hero_image");
startActivity(intent, options.toBundle());
In the called Activity:
// In onCreate, before setContentView
getWindow().requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
getWindow().setSharedElementEnterTransition(new ChangeImageTransform());
// In layout XML
<ImageView
android:id="@+id/detail_image"
android:transitionName="hero_image" />
14.14.3 SpringAnimation for Natural Motion¶
View view = findViewById(R.id.springy_view);
// Create a spring animation on translationY
SpringAnimation springAnim = new SpringAnimation(
view, DynamicAnimation.TRANSLATION_Y, 0f);
// Configure the spring
SpringForce spring = new SpringForce(0f)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
springAnim.setSpring(spring);
// Start with velocity from a fling gesture
springAnim.setStartVelocity(velocityFromFling);
springAnim.start();
14.14.4 Multi-Property AnimatorSet¶
View card = findViewById(R.id.card);
ObjectAnimator fadeIn = ObjectAnimator.ofFloat(card, View.ALPHA, 0f, 1f);
ObjectAnimator slideUp = ObjectAnimator.ofFloat(card, View.TRANSLATION_Y, 200f, 0f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(card, View.SCALE_X, 0.8f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(card, View.SCALE_Y, 0.8f, 1f);
AnimatorSet enterSet = new AnimatorSet();
enterSet.playTogether(fadeIn, slideUp, scaleX, scaleY);
enterSet.setDuration(350);
enterSet.setInterpolator(new DecelerateInterpolator());
enterSet.start();
14.14.5 Transition Framework: Scene Change¶
ViewGroup sceneRoot = findViewById(R.id.scene_root);
// Create a transition
TransitionSet transition = new TransitionSet();
transition.addTransition(new Fade(Fade.OUT));
transition.addTransition(new ChangeBounds());
transition.addTransition(new Fade(Fade.IN));
transition.setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
// Begin delayed transition (in-place)
TransitionManager.beginDelayedTransition(sceneRoot, transition);
// Now modify the view hierarchy
View viewToMove = findViewById(R.id.movable);
ViewGroup.LayoutParams params = viewToMove.getLayoutParams();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
viewToMove.setLayoutParams(params);
// The framework automatically captures end values and animates!
14.14.6 Tracing Animations with Perfetto¶
To capture animation frame timing in Perfetto:
# Record a Perfetto trace with animation-relevant categories
adb shell perfetto \
-c - --txt \
-o /data/misc/perfetto-traces/animation_trace.pb \
<<EOF
buffers: {
size_kb: 63488
fill_policy: DISCARD
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "sched/sched_switch"
ftrace_events: "power/suspend_resume"
atrace_categories: "view"
atrace_categories: "am"
atrace_categories: "wm"
atrace_categories: "anim"
atrace_categories: "gfx"
atrace_categories: "input"
atrace_apps: "your.app.package"
}
}
}
duration_ms: 10000
EOF
Key Perfetto tracks to examine:
| Track | What to Look For |
|---|---|
Choreographer#doFrame |
Frame timing, callback durations |
animator:XXX |
Individual animator updates |
animation |
Atrace section for animation callbacks |
RenderThread |
Native HWUI animation ticks |
SurfaceFlinger |
Composition timing |
Things to look for in the trace:
-
Frame drops: Gaps in the Choreographer doFrame track indicate skipped frames.
-
Long animation callbacks: If the ANIMATION callback phase takes more than 2-3ms, consider moving work off the UI thread.
-
RenderThread stalls: If RenderThread is blocked waiting for the UI thread, the staging sync is bottlenecked.
-
VSYNC alignment: Animation property updates should happen in the ANIMATION callback and be reflected in the same frame's TRAVERSAL pass.
14.14.7 Debugging Animation Issues¶
Common diagnostic tools and techniques:
# Enable animation duration scale via adb
adb shell settings put global animator_duration_scale 10.0 # 10x slowdown
# Reset to normal
adb shell settings put global animator_duration_scale 1.0
# Disable all animations (useful for testing)
adb shell settings put global animator_duration_scale 0
adb shell settings put global window_animation_scale 0
adb shell settings put global transition_animation_scale 0
# Dump running animations
adb shell dumpsys window animator
# Show surface update rectangles
adb shell setprop debug.hwui.show_dirty_regions true
14.14.8 AnimatedVectorDrawable in Practice¶
Create an animated checkmark that draws itself:
res/drawable/ic_check.xml (VectorDrawable):
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:height="24dp"
android:viewportWidth="24" android:viewportHeight="24">
<path
android:name="check"
android:pathData="M4.8,13.4 L9,17.6 L19.6,7"
android:strokeColor="#4CAF50"
android:strokeWidth="2"
android:strokeLineCap="round"
android:trimPathEnd="0"/>
</vector>
res/drawable/avd_check.xml (AnimatedVectorDrawable):
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_check">
<target
android:name="check"
android:animation="@animator/draw_check"/>
</animated-vector>
res/animator/draw_check.xml:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="trimPathEnd"
android:valueFrom="0"
android:valueTo="1"
android:duration="500"
android:interpolator="@android:interpolator/fast_out_slow_in"/>
In code:
ImageView imageView = findViewById(R.id.check_image);
imageView.setImageResource(R.drawable.avd_check);
AnimatedVectorDrawable avd = (AnimatedVectorDrawable) imageView.getDrawable();
avd.start();
14.14.9 Custom TypeEvaluator for Complex Types¶
For custom types, implement TypeEvaluator:
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
return new Point(
(int)(startValue.x + fraction * (endValue.x - startValue.x)),
(int)(startValue.y + fraction * (endValue.y - startValue.y))
);
}
}
// Usage:
ValueAnimator animator = ValueAnimator.ofObject(
new PointEvaluator(),
new Point(0, 0),
new Point(500, 500));
animator.addUpdateListener(anim -> {
Point p = (Point) anim.getAnimatedValue();
view.setX(p.x);
view.setY(p.y);
});
animator.setDuration(1000);
animator.start();
14.14.10 Keyframe Animation for Complex Timing¶
Create multi-segment animations with different timing per segment:
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(0.3f, 200f); // 30% of duration
kf1.setInterpolator(new AccelerateInterpolator());
Keyframe kf2 = Keyframe.ofFloat(0.7f, 150f); // 70% of duration
kf2.setInterpolator(new DecelerateInterpolator());
Keyframe kf3 = Keyframe.ofFloat(1f, 300f); // 100% of duration
PropertyValuesHolder pvh = PropertyValuesHolder.ofKeyframe(
View.TRANSLATION_Y, kf0, kf1, kf2, kf3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvh);
animator.setDuration(1500);
animator.start();
14.14.11 Reading Animation State from Dumpsys¶
The WindowManager dumpsys provides animation state information:
# Dump all animation state
adb shell dumpsys window animations
# Dump surface animator state
adb shell dumpsys window surfaces
# Dump transition state
adb shell dumpsys window transitions
# Shell transition state
adb shell dumpsys activity service SystemUIService WMShell
Key fields to examine:
mAnimationLayer-- The Z-order of the animation leashmLeash-- The SurfaceControl used for animationmAnimation-- The active AnimationAdaptermPendingAnimations/mRunningAnimations-- In SurfaceAnimationRunner
14.14.12 Animation Performance Best Practices¶
-
Prefer
ViewPropertyAnimatorand RenderNode properties for simple view animations -- they run on RenderThread and survive UI thread jank. -
Avoid allocations in update listeners.
AnimatorUpdateListenerruns every frame; allocating objects there triggers GC pauses. -
Use
DynamicAnimationfor gesture-driven motion. Springs and flings produce more natural results than fixed-duration animators when following user input. -
Cancel animations when views are detached. Leaked animations waste CPU and can crash when updating detached views.
-
Batch property changes. Multiple
ObjectAnimatorinstances on the same view can be combined into oneViewPropertyAnimatorcall or oneAnimatorSetto reduce overhead. -
Profile with Perfetto, not just visual inspection. A 60fps animation that drops occasional frames is invisible to the eye but measurable in traces.
14.14.13 ViewPropertyAnimator for Concise View Animation¶
ViewPropertyAnimator provides the most concise API for common View
animations. It is accessed through view.animate() and returns a builder:
view.animate()
.translationX(100f)
.translationY(200f)
.scaleX(1.5f)
.scaleY(1.5f)
.alpha(0.5f)
.rotation(45f)
.setDuration(500)
.setInterpolator(new OvershootInterpolator())
.setStartDelay(100)
.withStartAction(() -> Log.d("Anim", "Started"))
.withEndAction(() -> Log.d("Anim", "Ended"))
.start();
Under the hood, ViewPropertyAnimator creates RenderNodeAnimator instances
that run on the RenderThread, providing the best possible performance for
view property animations.
14.14.14 Gesture-Driven Animation with SpringAnimation¶
Implement a draggable view that springs back to its original position:
View draggable = findViewById(R.id.draggable);
float startX = draggable.getX();
float startY = draggable.getY();
SpringAnimation springX = new SpringAnimation(draggable, DynamicAnimation.X, startX);
springX.getSpring()
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_MEDIUM);
SpringAnimation springY = new SpringAnimation(draggable, DynamicAnimation.Y, startY);
springY.getSpring()
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_MEDIUM);
draggable.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
springX.cancel();
springY.cancel();
break;
case MotionEvent.ACTION_MOVE:
v.setX(event.getRawX() - v.getWidth() / 2f);
v.setY(event.getRawY() - v.getHeight() / 2f);
break;
case MotionEvent.ACTION_UP:
// Calculate velocity from VelocityTracker
springX.setStartVelocity(velocityX);
springY.setStartVelocity(velocityY);
springX.start();
springY.start();
break;
}
return true;
});
14.14.15 Transition Framework with Scenes¶
Build a two-scene transition with XML scenes:
res/layout/scene_a.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scene_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/box"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_gravity="start|top"
android:background="#FF4081"
android:transitionName="box"/>
</FrameLayout>
res/layout/scene_b.xml:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/scene_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/box"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="end|bottom"
android:background="#3F51B5"
android:transitionName="box"/>
</FrameLayout>
In code:
ViewGroup sceneRoot = findViewById(R.id.scene_root);
Scene sceneA = Scene.getSceneForLayout(sceneRoot, R.layout.scene_a, this);
Scene sceneB = Scene.getSceneForLayout(sceneRoot, R.layout.scene_b, this);
// Custom transition with arc motion
TransitionSet transition = new TransitionSet();
ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setPathMotion(new ArcMotion());
changeBounds.setDuration(500);
transition.addTransition(changeBounds);
transition.addTransition(new Recolor().setDuration(500));
// Toggle between scenes
boolean isSceneA = true;
button.setOnClickListener(v -> {
isSceneA = !isSceneA;
TransitionManager.go(isSceneA ? sceneA : sceneB, transition);
});
This produces a smooth animation where the box follows an arc path from top-left to bottom-right while simultaneously changing color and size.
14.14.16 FlingAnimation for Scroll-Like Motion¶
View card = findViewById(R.id.card);
// Create a fling animation with friction
FlingAnimation fling = new FlingAnimation(card, DynamicAnimation.TRANSLATION_X);
fling.setFriction(1.1f); // Higher = more friction, slower
fling.setMinValue(-500f); // Clamp to prevent going off screen
fling.setMaxValue(500f);
// Start from a velocity tracker (e.g., from a gesture)
VelocityTracker tracker = VelocityTracker.obtain();
// ... add motion events ...
tracker.computeCurrentVelocity(1000); // pixels per second
fling.setStartVelocity(tracker.getXVelocity());
fling.start();
// Chain with spring to snap to grid after fling
fling.addEndListener((animation, canceled, value, velocity) -> {
if (!canceled) {
float snapTarget = Math.round(value / 100f) * 100f;
SpringAnimation spring = new SpringAnimation(card,
DynamicAnimation.TRANSLATION_X, snapTarget);
spring.setStartVelocity(velocity);
spring.getSpring()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY);
spring.start();
}
});
14.14.17 Custom Interpolator¶
Build a custom interpolator that combines ease-in with a bounce:
public class EaseInBounceInterpolator extends BaseInterpolator
implements NativeInterpolator {
@Override
public float getInterpolation(float t) {
if (t < 0.6f) {
// Ease-in for first 60%
float normalized = t / 0.6f;
return 0.6f * (normalized * normalized * normalized);
} else {
// Bounce for last 40%
float normalized = (t - 0.6f) / 0.4f;
float bounce = (float)(Math.sin(normalized * Math.PI * 3) *
(1f - normalized) * 0.15f);
return 0.6f + 0.4f * normalized + bounce;
}
}
@Override
public long createNativeInterpolator() {
// For HWUI support, would need native implementation
// For now, fall back to Java-side interpolation
return 0;
}
}
14.14.18 Window Insets Animation¶
Animate keyboard appearance with WindowInsetsAnimation (API 30+):
// In Activity or Fragment
view.setWindowInsetsAnimationCallback(
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@Override
public void onPrepare(@NonNull WindowInsetsAnimation animation) {
// Capture pre-animation state
}
@NonNull
@Override
public WindowInsets onProgress(@NonNull WindowInsets insets,
@NonNull List<WindowInsetsAnimation> runningAnimations) {
// Find the keyboard animation
for (WindowInsetsAnimation anim : runningAnimations) {
if ((anim.getTypeMask() & WindowInsets.Type.ime()) != 0) {
float progress = anim.getInterpolatedFraction();
// Translate view to follow keyboard
float offset = insets.getInsets(WindowInsets.Type.ime()).bottom;
view.setTranslationY(-offset * progress);
}
}
return insets;
}
@Override
public void onEnd(@NonNull WindowInsetsAnimation animation) {
// Animation complete, clean up
view.setTranslationY(0);
}
});
14.14.19 Multi-Property Physics Animation¶
Chain spring animations for a natural "rubber band" effect:
View bubble = findViewById(R.id.bubble);
// X spring - tracks finger X
SpringAnimation springX = new SpringAnimation(bubble, DynamicAnimation.TRANSLATION_X);
springX.getSpring()
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
// Y spring - tracks finger Y with different stiffness
SpringAnimation springY = new SpringAnimation(bubble, DynamicAnimation.TRANSLATION_Y);
springY.getSpring()
.setStiffness(SpringForce.STIFFNESS_VERY_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
// Scale spring - grows on touch
SpringAnimation springScale = new SpringAnimation(bubble, DynamicAnimation.SCALE_X);
springScale.getSpring()
.setStiffness(SpringForce.STIFFNESS_MEDIUM)
.setDampingRatio(SpringForce.DAMPING_RATIO_HIGH_BOUNCY);
// Link scale X and Y
springScale.addUpdateListener((anim, value, velocity) -> {
bubble.setScaleY(value);
});
bubble.setOnTouchListener((v, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
springX.animateToFinalPosition(event.getRawX() - v.getWidth() / 2f);
springY.animateToFinalPosition(event.getRawY() - v.getHeight() / 2f);
springScale.animateToFinalPosition(1.3f);
break;
case MotionEvent.ACTION_MOVE:
springX.animateToFinalPosition(event.getRawX() - v.getWidth() / 2f);
springY.animateToFinalPosition(event.getRawY() - v.getHeight() / 2f);
break;
case MotionEvent.ACTION_UP:
springX.animateToFinalPosition(0f);
springY.animateToFinalPosition(0f);
springScale.animateToFinalPosition(1.0f);
break;
}
return true;
});
14.14.20 Perfetto Trace Analysis Walkthrough¶
After capturing a trace with animation categories, open it in ui.perfetto.dev and look for these patterns:
Healthy Animation Frame:
|--- Choreographer#doFrame (16.6ms) ---|
|-- INPUT (0.5ms) --|
|-- ANIMATION (1.2ms) --|
|-- TRAVERSAL (8ms) --|
|-- COMMIT (0.1ms) --|
Janky Animation Frame:
|--- Choreographer#doFrame (45ms) ---|
|-- INPUT (0.5ms) --|
|-- ANIMATION (1.2ms) --|
|-- TRAVERSAL (35ms) --| <-- Heavy layout causing jank
|-- COMMIT (0.3ms) --| <-- Start time adjusted
RenderThread Animation (no UI thread involvement):
UI Thread: (idle)
RenderThread:
|-- syncFrameState --|
|-- AnimatorManager.animate (0.2ms) --|
|-- drawRenderNode --|
Key metrics to track:
- Frame-to-frame time (should be ~16.6ms at 60Hz, ~8.3ms at 120Hz)
- ANIMATION callback duration (should be < 2ms)
- Time between ANIMATION callback and frame presentation
Summary¶
Android's animation system spans four generations of Java APIs, a native RenderThread engine, and a Shell process animation coordinator:
| Layer | Key Class | Thread | Scope |
|---|---|---|---|
| View Animation | Animation |
UI | Visual-only transforms |
| Property Animation | ValueAnimator, ObjectAnimator |
UI | Real property changes |
| Transition Framework | Transition, TransitionManager |
UI | Scene-change choreography |
| Shell Transitions | Transitions, DefaultTransitionHandler |
Shell | Cross-window WM transitions |
| Physics Animation | SpringAnimation, FlingAnimation |
UI | Force-driven motion |
| HWUI Animation | BaseRenderNodeAnimator |
RenderThread | Jank-free RenderNode properties |
| Predictive Back | BackAnimationController |
Shell | Gesture-driven back previews |
Choreographer ties it all together, receiving VSYNC from the display and dispatching the ordered callback chain (INPUT -> ANIMATION -> INSETS_ANIMATION -> TRAVERSAL -> COMMIT) that produces each frame.
The evolution from View Animation's matrix-only transforms to the Shell Transition system's coordinated cross-window animations reflects Android's journey from single-window phone UI to multi-window, foldable, desktop-class computing. Understanding each layer's role and limitations is essential for building smooth, responsive Android applications.
Historical Evolution Timeline¶
timeline
title Android Animation System Evolution
section API 1-10
2008 : View Animation AlphaAnimation, TranslateAnimation, etc.
2009 : LayoutAnimationController
2010 : AnimationDrawable improvements
section API 11-20
2011 : Property Animation ValueAnimator, ObjectAnimator
2012 : LayoutTransition improvements
2013 : Transition Framework Scene, TransitionManager
2014 : Material transitions, shared elements, PathInterpolator
2014 : HWUI RenderThread animations
section API 21-30
2015 : AnimatedVectorDrawable on RenderThread
2016 : Physics-based animations SpringAnimation, FlingAnimation
2017 : SurfaceAnimator leash pattern in WM
2019 : WindowInsetsAnimation
2020 : WindowManager refactoring
section API 31+
2021 : Shell Transitions architecture
2022 : Predictive Back animations
2023 : Predictive Back cross-activity/task
2024 : Enhanced foldable animations, desktop mode
Decision Guide: Which Animation API to Use¶
flowchart TD
A[Need to animate] --> B{What are you animating?}
B -->|View properties| C{Simple or complex?}
B -->|Arbitrary object properties| D[ObjectAnimator]
B -->|VectorDrawable paths| E[AnimatedVectorDrawable]
B -->|View hierarchy changes| F[Transition Framework]
B -->|Activity/Fragment enter/exit| G[Activity Transitions]
B -->|Response to gesture| H[SpringAnimation / FlingAnimation]
B -->|Window-level system animation| I[Shell Transitions]
C -->|Simple: alpha, translate, scale| J[ViewPropertyAnimator]
C -->|Complex: multiple properties, timing| K[AnimatorSet]
J --> L[Runs on RenderThread - best perf]
D --> M[Runs on UI thread]
E --> N[Runs on RenderThread API 25+]
F --> O[Automatic diffing]
G --> P[Cross-activity coordination]
H --> Q[No fixed duration - physics based]
I --> R[Cross-window SurfaceControl]
K --> M
Animation and Accessibility¶
Android's animation system integrates with accessibility services in several important ways:
-
Duration scale of 0 disables all animations: When
animator_duration_scaleis 0,ValueAnimator.areAnimatorsEnabled()returns false. Apps should check this and skip to final states immediately. -
Reduce motion preference: Starting in Android 12, apps can detect the "Remove animations" accessibility setting and adjust their animation strategy accordingly.
-
Transition suppression: The Transition Framework respects the animation scale. When animations are disabled, transitions complete instantly.
-
TalkBack integration: Screen reader users benefit from reduced motion, as animations can interfere with focus traversal and content announcements.
Best practice:
if (!ValueAnimator.areAnimatorsEnabled()) {
// Skip to final state immediately
view.setAlpha(1f);
view.setTranslationX(0f);
} else {
// Run normal animation
view.animate().alpha(1f).translationX(0f).start();
}
Thread Safety Considerations¶
The animation system has specific threading requirements:
| Component | Thread Requirement |
|---|---|
| ValueAnimator.start() | Must be called on a Looper thread |
| ObjectAnimator | Same as ValueAnimator; setter called on same thread |
| ViewPropertyAnimator | Must be called on UI thread |
| RenderThread animations | Initiated from UI thread, run on RenderThread |
| SurfaceAnimationRunner | Runs on AnimationThread |
| Shell transitions | Runs on Shell main thread |
| SpringAnimation | Must be called on a Looper thread |
Attempting to start an animator from a non-Looper thread throws:
Memory and Resource Considerations¶
Animation objects can hold references that prevent garbage collection:
-
AnimatorListener references: Listeners hold strong references to their enclosing class. Use
AnimatorListenerAdapter(which has empty default implementations) to avoid requiring all callback methods. -
AnimationHandler leaks: Running animators hold strong references through AnimationHandler. Always cancel animations in
onDestroy()oronDetachedFromWindow(). -
Transition memory: The Transition Framework captures view state (including potentially large bitmaps for shared elements). These are released when the transition completes.
-
Surface leash cleanup: In the WM, animation leashes are surfaces that consume compositor memory. The
SurfaceAnimator.reset()method releases the leash when animation completes.
Animation Testing¶
The animation system provides several testing hooks:
// Speed up all animations for faster test execution
ValueAnimator.setDurationScale(0f); // Instant completion
// Use custom AnimationHandler for deterministic timing
AnimationHandler testHandler = new AnimationHandler();
testHandler.setProvider(new TestAnimationFrameCallbackProvider());
AnimationHandler.setTestHandler(testHandler);
// Advance animation to specific time
testHandler.doAnimationFrame(targetTimeMs);
For Espresso UI tests:
// In test setup
@Before
public void disableAnimations() {
// These need ADB shell permissions in a real test
Settings.Global.putFloat(resolver, Settings.Global.WINDOW_ANIMATION_SCALE, 0f);
Settings.Global.putFloat(resolver, Settings.Global.TRANSITION_ANIMATION_SCALE, 0f);
Settings.Global.putFloat(resolver, Settings.Global.ANIMATOR_DURATION_SCALE, 0f);
}
Common Animation Pitfalls¶
-
Starting animations in onResume(): This can cause flickering because the view hierarchy may not be fully laid out. Use
view.post()orViewTreeObserver.OnPreDrawListenerinstead. -
Not cancelling on config change: Animations that hold view references will crash after rotation if not cancelled in
onPause()or similar. -
Over-animating: Running many simultaneous animators (>20) can cause frame drops even on modern devices. Batch properties with
ViewPropertyAnimatororAnimatorSet. -
Animating layout properties: Animating
width/heighttriggersrequestLayout()every frame, which is expensive. PreferscaleX/scaleYorsetClipBounds(). -
Wrong interpolator: Using
LinearInterpolatorfor UI motion looks robotic. UseFastOutSlowInInterpolator(Material Design default) for most UI animations. -
Ignoring duration scale: Hard-coded delays that do not respect
sDurationScalewill appear too long when animations are sped up and too short when slowed down.
Key Source File Cross-Reference¶
| Section | Primary Source Files |
|---|---|
| 10.2 View Animation | frameworks/base/core/java/android/view/animation/Animation.java (1,363 lines) |
frameworks/base/core/java/android/view/animation/AnimationSet.java (553 lines) |
|
frameworks/base/core/java/android/view/animation/PathInterpolator.java (245 lines) |
|
| 10.3 Property Animation | frameworks/base/core/java/android/animation/ValueAnimator.java (1,821 lines) |
frameworks/base/core/java/android/animation/ObjectAnimator.java (1,004 lines) |
|
frameworks/base/core/java/android/animation/AnimatorSet.java (2,280 lines) |
|
frameworks/base/core/java/android/animation/PropertyValuesHolder.java (1,729 lines) |
|
frameworks/base/core/java/android/animation/AnimationHandler.java (579 lines) |
|
| 10.4 Transition Framework | frameworks/base/core/java/android/transition/Transition.java (2,451 lines) |
frameworks/base/core/java/android/transition/TransitionManager.java (470 lines) |
|
frameworks/base/core/java/android/transition/ChangeBounds.java (~500 lines) |
|
frameworks/base/core/java/android/transition/Fade.java (~200 lines) |
|
| 10.6 WM Animations | frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java (647 lines) |
frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java (359 lines) |
|
frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java (365 lines) |
|
| 10.7 Shell Transitions | frameworks/base/libs/WindowManager/Shell/src/.../transition/Transitions.java (1,964 lines) |
frameworks/base/libs/WindowManager/Shell/src/.../transition/DefaultTransitionHandler.java (1,081 lines) |
|
| 10.8 Predictive Back | frameworks/base/libs/WindowManager/Shell/src/.../back/BackAnimationController.java |
| 10.9 Physics Animation | frameworks/base/core/java/com/android/internal/dynamicanimation/animation/SpringAnimation.java |
frameworks/base/core/java/com/android/internal/dynamicanimation/animation/SpringForce.java |
|
frameworks/base/core/java/com/android/internal/dynamicanimation/animation/DynamicAnimation.java |
|
| 10.10 HWUI Animation | frameworks/base/libs/hwui/Animator.cpp (~460 lines) |
frameworks/base/libs/hwui/AnimatorManager.cpp (~207 lines) |
|
| 10.11 Drawable Animation | frameworks/base/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java (~1,870 lines) |
| 10.12 Choreographer | frameworks/base/core/java/android/view/Choreographer.java (1,714 lines) |
Glossary of Animation Terms¶
| Term | Definition |
|---|---|
| Animator | Abstract base class for all property animations |
| Animation | Abstract base class for view animations (legacy) |
| AnimationHandler | Per-thread manager for animation frame callbacks |
| AnimatorSet | Orchestrates multiple animators with timing dependencies |
| Choreographer | VSYNC-driven callback dispatcher for frame-synchronized work |
| DynamicAnimation | Base class for physics-based animations |
| Evaluator | Computes intermediate values between keyframes |
| Fraction | Progress through an animation cycle, 0.0 to 1.0 |
| Interpolator | Maps linear time fraction to non-linear fraction |
| Keyframe | A value at a specific fraction of the animation |
| Leash | Temporary SurfaceControl parent used during WM animations |
| PathMotion | Defines the motion path for position animations |
| Propagation | Controls staggered start delays in transitions |
| PropertyValuesHolder | Binds a property name to its keyframes and evaluator |
| RenderNode | Native drawing container with animatable properties |
| Scene | Snapshot of a view hierarchy for transitions |
| SpringForce | Damped harmonic oscillator physics model |
| Staging | HWUI pattern for transferring state from UI to RenderThread |
| SurfaceControl | Handle to a compositor surface in SurfaceFlinger |
| Transaction | Atomic batch of SurfaceControl operations |
| Transformation | Matrix + alpha result of a view animation |
| Transition | Detects and animates property changes between scenes |
| TransitionInfo | Describes participating windows in a shell transition |
| VSYNC | Vertical synchronization signal from the display |
Performance Metrics¶
Key metrics for animation performance evaluation:
| Metric | Target | Source |
|---|---|---|
| Frame duration | < 16.67ms (60Hz) / < 8.33ms (120Hz) | Perfetto Choreographer#doFrame |
| Animation callback time | < 2ms | Perfetto CALLBACK_ANIMATION |
| Jank rate | < 1% of frames | JankTracker / FrameMetrics |
| Surface frame latency | < 2 VSYNC periods | SurfaceFlinger stats |
| Animation start latency | < 1 frame | Time from start() to first visible frame |
| Transition duration | 150-500ms (typical) | TransitionMetrics |
| Spring settle time | < 500ms | SpringForce threshold crossing |
Further Reading¶
For deeper exploration of animation internals, examine these additional source files:
frameworks/base/core/java/android/view/ViewPropertyAnimator.java-- The concise view animation APIframeworks/base/core/java/android/view/RenderNodeAnimator.java-- JNI bridge to HWUI animationsframeworks/base/libs/hwui/Interpolator.h-- Native interpolator declarationsframeworks/base/libs/hwui/PropertyValuesAnimatorSet.cpp-- Native AnimatorSet for AVDframeworks/base/core/java/android/widget/Scroller.java-- Fling physics for scrollingframeworks/base/core/java/android/widget/OverScroller.java-- Overscroll with elastic edge effectsframeworks/base/services/core/java/com/android/server/wm/Transition.java-- WM server-side transition state machineframeworks/base/services/core/java/com/android/server/wm/TransitionController.java-- WM transition lifecycle managerframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/TransitionAnimationHelper.java-- Animation resource loadingframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultSurfaceAnimator.java-- Surface animation builderframeworks/base/core/java/android/app/ActivityTransitionCoordinator.java-- Cross-activity shared element coordinationframeworks/base/core/java/android/app/ActivityOptions.java-- Activity launch animation optionsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt-- Predictive back cross-activity animationframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java-- Predictive back cross-task animationframeworks/base/core/java/com/android/internal/dynamicanimation/animation/FlingAnimation.java-- Fling physics implementationframeworks/base/core/java/com/android/internal/dynamicanimation/animation/Force.java-- Force interface for custom physicsframeworks/base/core/java/android/animation/Keyframe.java-- Time/value pair for keyframe animationsframeworks/base/core/java/android/animation/KeyframeSet.java-- Ordered collection of keyframes with interpolationframeworks/base/core/java/android/transition/Visibility.java-- Base class for appear/disappear transitionsframeworks/base/core/java/android/transition/TransitionSet.java-- Container for ordered transition groupsframeworks/base/core/java/android/transition/Scene.java-- View hierarchy snapshot for transitionsframeworks/base/core/java/android/view/animation/Transformation.java-- Matrix + alpha transform resultframeworks/base/core/java/android/view/animation/AnimationUtils.java-- Animation loading and timing utilitiesframeworks/base/services/core/java/com/android/server/wm/WindowAnimationSpec.java-- Wraps view Animation for SurfaceControlframeworks/base/services/core/java/com/android/server/wm/LocalAnimationAdapter.java-- Adapter for WM-local animationsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHandler.java-- Handles overlapping transitionsframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java-- Delegates transitions to external apps
Relationship to Other Chapters¶
This chapter connects to several other topics covered in this book:
-
Chapter 9 (Graphics Render Pipeline): HWUI animations run on the RenderThread described in the graphics chapter. Understanding display lists, RenderNodes, and the GPU pipeline is essential for understanding why RenderThread animations are jank-free.
-
Chapter 7 (Binder IPC): Shell transitions use Binder to communicate between the system_server (WindowManager) and the Shell process. The
ITransitionPlayerinterface is a Binder interface, andTransitionInfois a Parcelable transferred across the process boundary. -
Chapter 3 (Boot and Init): The animation system is initialized during system server startup. The
WindowManagerServicecreates theWindowAnimator,SurfaceAnimationRunner, and animation threads during boot. -
Chapter 4 (Kernel): VSYNC signals originate from the display hardware driver and are delivered through the kernel to userspace via the
DisplayEventReceiver->Choreographerpipeline.
The animation system sits at the intersection of application framework, system services, and graphics pipeline, making it one of the most cross-cutting subsystems in AOSP.