VectorDrawable Adaptive Icons

So the Android O APIs are final — API 26 is here! That means you can start compiling with API 26 right now (and you should really always compile with the latest SDK) as well as work towards targeting API 26.

One of the first things you should consider working on is providing an adaptive icon — an icon made of a separate background and foreground layer. This new format provides a consistent shape across all icons on a device and will also allow launchers to add visual effects by animating the background and foreground separately. This is really how you make a good first impression for users with Android O devices.

With the system handling the outer edge shape and its shadow, adaptive icons give you a chance to re-evaluate how you build your app icon. If you’re able to build your app icon as an SVG/vector image, consider avoiding bloating your app with more PNGs for the background and foreground and take advantage of VectorDrawables for your adaptive icon.

Note: there are valid cases where PNGs are the correct choice for your app. Talk to your designer before trying to force everything into vector drawables.
VectorDrawable Adaptive Icons for Muzei

I’ll walk you through what it took to convert Muzei’s current icon into an adaptive icon.

Note: you do not need to target API 26 to provide an adaptive icon — only compile against it. Users will benefit from your work even as you work through other behavior changes.

The basics of adaptive icons

Let’s assume your Android Manifest’s <application> tag has android:icon="@mipmap/ic_launcher". To add an adaptive icon that replaces those PNGs on API 26+ devices, you’ll add a res/mipmap-anydpi-v26/ic_launcher.xml file that looks like this:

<adaptive-icon
xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

By placing it in the mipmap-anydpi-v26 folder, the resource system will use it in preference over any files in the other dpi folders (exactly what you want, since this file is replacing all of them) and that it should only be used in API 26+ devices.

You’ll notice that the drawables are in the drawable directory. This is because I’m using vector drawables. If you are using PNGs, this should most definitely be in mipmap. Another option is use a color resource for your background:

<adaptive-icon
xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/boring_white"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Round icons

For those of you with an android:roundIcon, you must keep that attribute to continue to support API 25 devices. Devices with round masks will use your custom roundIcon if available, but it is strongly suggested to use a single adaptive icon for those devices as well (this ensures you get the standard shadow, support for visual effects, etc).

I find this easiest by creating an alias resource. You’d keep your ic_launcher_round images in the res/mipmap directories for API 25 devices, but add a file in your values-anydpi-v26 folder:

<resources>
<mipmap name="ic_launcher_round">@mipmap/ic_launcher</mipmap>
</resources>

Building a VectorDrawable Background

The background is a great place to start. Being a full bleed 108 x 108 dp image, there’s not much special about this one:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="48.0"
android:viewportHeight="48.0">
<!-- Your background vector -->
</vector>

Just keep in mind that due to the masking of the image, users will usually only see the middle 72 x 72 dp.

Building a VectorDrawable Foreground

The foreground image, on the other hand, offers a unique challenge.

First, you have to handle creating the foreground shape itself. It is still on the same 108 x 108 dp artboard, but the ‘safe zone’ of what you know will be shown is only a middle circle of radius 33 dp — don’t put anything critical outside of that part!

Where I initially got stuck was when it comes to the traditional 45º material cast shadow that the foreground also needs to include. Normally, this would be where you’d have to break out the PNGs to get that perfect gradient. But gradient support was added to VectorDrawables back to API 24!

Getting gradients working, however, required a bit of research. The VectorDrawable docs point out the ability to use a GradientColor, but where to find a good example? On StackOverflow of course.

Now, Roman Nurik, the maker of the original Muzei icon, had made a fantastic SVG version of the launcher icon including the shadow as a <linearGradient> element in the SVG. I won’t pretend that I know how he came up with the correct values — talk to your local designers or read the material design guidelines for product icons a couple hundred times.

But what it became was a res/color/ic_launcher_shadow.xml file that looks like this:

<gradient xmlns:android="http://schemas.android.com/apk/res/android"
android:endColor="#0000"
android:endX="36.814"
android:endY="41.655"
android:startColor="#F000"
android:startX="18.691"
android:startY="13.748"
android:type="linear"/>

You’ll note all of the attributes from the GradientColor documentation available to customize this to be just right.

Note: As of Android Studio 3.0 Canary 3, Android Studio flags gradient as an error (‘Element gradient must be declared’). It still works.

Now, we can refer to our gradient color the same way you’d refer to a color: with @color/ic_launcher_shadow in the VectorDrawable. In Muzei’s case, this means that the foreground background consists of two paths: the shadow and then the shape below it:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="108dp"
android:viewportHeight="48.0"
android:viewportWidth="48.0"
android:width="108dp">
<path
android:fillColor="@color/ic_launcher_shadow"
android:pathData="<your shadow's path>"/>
<!-- This is your shape drawn on top of your shadow -->
<path
android:pathData="<your path>"/>
</vector>
Note: this is also how you’d add a finish layer to your icon — a separate gradient above your shape as explained in the material design guidelines.

All the benefits, none of the APK bloat

By taking advantage of API 24’s addition of gradients to VectorDrawables, we can build well designed VectorDrawable adaptive icons that have the correct material shadows on foreground elements without resorting to adding even more PNGs into the app.

If you want to see it in action, join Muzei’s open beta, then download Muzei on an Android O Dev Preview 3 device and check out the commit that added the adaptive icon. (Although unless you have the superhuman ability to visualize VectorDrawables like Nick Butcher, you might find the full commit doesn’t add much over the code we already covered.)

For further information, you might want to check out Nick Butcher’s excellent series of articles on adaptive icons.

App Shortcut Adaptive Icons

As mentioned in the adaptive icon page, you should also convert your App Shortcuts to use adaptive icons. The same rules apply here: create a new v26 directory and split your shortcut into a background and foreground (see Muzei’s commit that does exactly that).

If you’re building a dynamic shortcut using a Bitmap, you might find the Support Library 26.0.0-beta2’s IconCompat.createWithAdaptiveBitmap() useful in ensuring that your Bitmap is masked correctly to match other adaptive icons.