Implementing Adaptive Icons

Android O introduces an new application icon format called adaptive icons, intended to make all icons on a device more coherent. This post will look into how to build adaptive icons for your app. It’s unlikely that many apps will be minSdkVersion 26 any time soon, so this post will also examine techniques for adding this additional icon as efficiently as possible.

It’s also worth pointing out that Android Studio 3.0 includes a new wizard to help you to create adaptive icons which we won’t cover here; we’ll stick to the fundamental format and techniques.

If you’re interested in the back story of this format or how to design an adaptive icon the check out these posts:

Basics

Adaptive icons are a new drawable type, namely AdaptiveIconDrawable. You’ll likely never need to work with the class directly, but to define it in XML and point to it from your manifest. You can do so using this format:

<adaptive-icon>
<background android:drawable="@[drawable|mipmap|color]/bar"/>
<foreground android:drawable="@[drawable|mipmap|color]/foo"/></adaptive-icon>

Each drawable must be 108dp*108dp in size; background drawables must be opaque whilst foregrounds can contain transparency.

Actually minSDK is 26

Because adaptive icons are only used on API 26+, you can rely on certain features being available to you. Specifically pretty capable VectorDrawable support.

Unfortunately you can’t use custom drawable inflation; as your icon will be loaded by other apps’ processes, you need to stick to platform drawable types.

Utilizing vectors is attractive as it allows us to specify the drawable once in a very compact format. That means it will be crisp at every density without bloating your APK.

In particular, many developers do not seem to have taken advantage of VectorDrawable's support for gradients. On this topic, I’d recommend reading Ian Lake’s recent post on implementing an adaptive icon which covers the basics.

Ian shows how to use a simple linear gradient, but VectorDrawable has a few more nifty tricks. Here’s an example of implementing a ‘long-shadow’ using a radial gradient with multiple color stops. I’m also using the inline resource syntax which lets you embed what would be multiple files into a single file (via AAPT tricks, commonly used with AnimatedVectorDrawables):

<vector ...>
  <path android:name="long-shadow"
android:pathData="...">
    <aapt:attr name="android:fillColor">
      <gradient
android:type="radial"
android:centerX="54"
android:centerY="54"
android:gradientRadius="76.37">
        <!-- 15% black from center to 32% stop -->
<item android:offset="0.0" android:color="#26000000" />
<item android:offset="0.32" android:color="#26000000" />
        <!-- 2% black at 62% stop -->
<item android:offset="0.62" android:color="#05000000" />
        <!-- fade to transparent -->
<item android:offset="1.0" android:color="#00000000" />
      </gradient>
    </aapt:attr>
  </path>
  ...
</vector>
The shadow produced by the radial gradient.

Most icons include some kind of drop-shadow element in them (per the material guidelines) which unfortunatelyVectorDrawable does not support. With adaptive icons, there are two features that make vectors more relevant:

  1. The launcher is now responsible for masking the overall drawable and providing any drop shadow for the full shape. You no longer have to bake in a shadow for the entire shape.
  2. The icon is comprised of a background and a foreground image, so if one of those layers does not require any shadows, then it can take advantage of vectors.

Some simple shadows can be approximated using gradients but unfortunately not everything.

Minimum viable raster

If you can’t implement your design with vectors then it’s perfectly fine to do so using PNGs. Your launcher icon is such a crucial asset that it’s definitely worth a few extra bytes to make the right impression.

There is however a neat trick that you can utilize for assets with areas of transparency in them… which is somewhat common in adaptive icon foregrounds. While this kind of asset likely compresses well at build time, at run time each pixel takes up 8 bits of memory no matter what the opacity. To minimize this, if the transparency is around the edges, you can trim these areas from the PNG and use an InsetDrawable to wrap it and fill it out to its 108dp size. Now unfortunately InsetDrawable doesn’t love being resized (i.e. if you set a top inset of 16dp it will always be 16dp no matter how the drawable’s bounds are resized) so in API26 fractional insets were added to mitigate this. This lets you specify insets as a percentage of the overall drawable so they will scale correctly.

For example, say you have a foreground asset which is 54dp*54dp; instead of placing that in a 108dp*108dp asset amidst transparency you can do the following.

<inset ...
android:drawable="@mipmap/ic_fg_trimmed"
android:insetLeft="25%"
android:insetTop="25%"
android:insetRight="25%"
android:insetBottom="25%" />

Here’s an example using this technique:

No need to ship/load transparent pixels

Note that you’ll still have to provide the trimmed raster asset at different densities, but at least each will be smaller and in-memory size will be much reduced.

Take a shortcut

Adaptive icons aren’t solely for app icons, they’re also used for app shortcuts. App shortcuts can be pinned to the homescreen so they need to fit in with app icons. The (pre-O) design specs call for shortcut icons to sit on a grey circular background. In Android-O, the background should fill the adaptive icon mask. If you don’t update to adaptive, your shortcut icon will be scaled down and placed on a white background.

Plaid’s search shortcut; before and after becoming adaptive

To implement this in my app Plaid, I initially added new icons in the v26 configuration, re-drawn for the adaptive grid and keylines. I wasn’t happy with this approach as they were essentially scaled versions of the v25 icons; meaning I now had two icons to maintain. Ultimately I decided to break the v25 icon into a foreground (e.g. the search icon) and background (the grey circle) and combine them with a LayerDrawable:

<layer-list ...>
  <item android:drawable="@drawable/ic_app_shortcut_background"/>
  <item android:drawable="@drawable/ic_shortcut_search_foreground"/>
</layer-list>

I could then use the same foreground asset in the adaptive icon. On v25, app shortcut icons are 24dp within a 48dp asset; on v26 they’re 44dp within a 108dp asset:

App shortcut icon foreground assets required on API 25 & API 26

To use the same 48dp file I needed to inset it so that the icon is the correct size once it’s scaled up (yay vectors!) to the 108dp adaptive icon size. The background is achieved using a ColorDrawable:

<adaptive-icon ...>
  <background android:drawable="@color/light_grey" />
  <foreground>
    <!-- 10dp padding on each side of 108dp asset -->
<inset
android:drawable="@drawable/ic_shortcut_search_foreground"
android:inset="9.26%" />
  </foreground>
</adaptive-icon>
AdaptiveIconDrawable will scale the supplied asset to 108dp, so to calculate the inset required to produce a 44dp icon: 48 / 24 * 44 = 88; that is we need to inset the scaled up asset by 10dp each side: 10 / 108 → 9.26%

For bitmap image shortcuts, use Icon#createWithAdaptiveBitmap.

Play around

If you’re building an adaptive icon, then the Adaptive Icon Playground app might be useful to you. It lets you preview adaptive icons on your device, see how they look with different masks applied and explore some motion effects.

You can grab an APK (for devices already running Android-O) or check it out on github:

Adapt away

Hopefully these tips will help you to build awesome adaptive icons that will make your app a better resident of your user’s device. If you’ve got any tips for implementing adaptive icons, then let me know in the comments.