Before Lollipop you might have noticed that animating view properties smoothly while doing “heavy” work, like transitioning between different Activity, was essentially impossible. And yet, from Lollipop onwards, those animations and other effects (like ripples) keep animating as if nothing was happening. The trick is a little help from the RenderThread.

The real “executor” of the rendering is the GPU, which by itself doesn’t know anything about animations: the only way to animate something is to issue different drawing commands at each frame, and that logic cannot run on the GPU itself. When this is done on the UI thread, any heavy work will prevent the new drawing commands from being issued in time, hence the lag in updating whatever is being animated.

It was mentioned before that the RenderThread can manage certain aspects of the display list pipeline, but it’s important to note that the creation and modification of the display lists still has to happen on the UI thread.

Then how can animations be updated from a different thread?

When drawing with hardware acceleration, the Canvas implementation is a class called DisplayListCanvas (formerly GLES20Canvas) which has overloads of some drawing methods that, instead of accepting a direct value, expect a reference to a CanvasProperty, which in turn is a wrapper for such value. This way the display list can still be created on the UI thread with a static set of drawing calls, but the parameters for such calls can be changed dynamically (and asynchronously on the RenderThread) through the CanvasProperty mapping.

There’s one more step: the value of a CanvasProperty needs to be animated through a RenderNodeAnimator, which is how the animation is configured and started.

Some interesting properties of the resulting animation: