These two lights cast each their own shadow, one which is mostly affecting the bottom edge of a material sheet (key light), and the other which is affecting all edges (ambient light):

Image derived from the Material Design guidelines.

The elevation property directly controls the shape of the resulting shadow; you can see this clearly with buttons, which change their elevation based on their state:

Image from Vadim Gromov’s Dribbble.

You may think that the elevation property is the only way to control how shadows look, but that’s not true.

In Android, there is a very little known API called Outline that is providing the required information for a Material sheet to project a shadow. The default behaviour for View s is to delegate the outline definition to their background drawable. ShapeDrawable s for example provide outlines that match their shapes, while ColorDrawable s, BitmapDrawable s, etc. provide a rectangle matching their bounds. But nothing says we cannot change that, and tell a view to use a different ViewOutlineProvider , using the setOutlineProvider() method:

view.outlineProvider = outlineProvider

If we control the ViewOutlineProvider , we can tweak the resulting Outline , tricking the OS into drawing whatever shadow we want:

You can use elevation and Outline to do all sorts of tweaks to the shape and position of an elevation shadow:

Believe it or not, I have actually captured this one myself on my phone.

You will notice how the shadow here does not just adapt to different elevation values, but is also translated around and gets a larger or smaller size than the view itself.

Another thing you can do is to assign a shape that is different from the actual outline of the view itself — I cannot think of any situation in which this would make sense, but you could. The only limitation is that the shape must be convex. There are convenience methods on Outline to have ellipses, rectangles and rounded rectangles, but you could also use any arbitrary Path , as long as it’s convex.

Unfortunately it’s not possible to exaggerate some effects too much, since as you can see there are some shortcuts the system takes when rendering the shadows which will create some rather annoying artefacts when you hit them.

In case you are curious how shadows are rendered in Android, the relevant code is in the hwui package in AOSP — you can start looking at AmbientShadow.cpp .

Another limitation is that we cannot tint the elevation shadow, we’re stuck with the default grey, but to be honest I don’t believe that’s a bad thing 😉

Elevation tweaks in action

I’ve used this technique to come up with a peculiar elevation appearance for the cards in Squanchy, an open source conference app I’ve been working on in the last year:

As you can see, the cards have a shadow that looks way more diffuse than the regular elevation shadows. This is obtained by having an Outline that is 4dp smaller than the card, and an elevation of 4dp :

The cards have an android:stateListAnimator that also tweaks their elevation and translationZ based on their pressed state, like Material buttons do. You can see how the cardInset* attributes are then used in the CardLayout code to shrink the Outline that we provide to the system.

When you scroll the schedule in Squanchy, you might notice that the shadow tends to change size as a card scrolls along the Y axis:

If the effect is too subtle in a gif for you to see, this image makes it crystal clear: