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:
Android O introduces a new format for app icons called adaptive icons. To better understand the motivation and…medium.com
Android O introduces a new app icon format: adaptive icons. Adaptive icons can make devices more coherent by unifying…medium.com
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:
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
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.
With Android O finalized as API 26, now’s the time to start thinking about adding an adaptive icon without the bloat of…medium.com
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
<!-- 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" />
Most icons include some kind of drop-shadow element in them (per the material guidelines) which unfortunately
VectorDrawable does not support. With adaptive icons, there are two features that make vectors more relevant:
- 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.
- 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.
Here’s an example using this technique:
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.
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
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:
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
<background android:drawable="@color/light_grey" />
<!-- 10dp padding on each side of 108dp asset -->
AdaptiveIconDrawablewill 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.
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:
AdaptiveIconPlayground - An Android app for experimenting with Adaptive Icons.github.com
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.