Background
Starting from Android O, apps can have adaptive icons, which are 2 layers of drawables: foreground and a background. The background is a mask that gets to be a shape of the launcher/user's choice, while the OS has a default shape for it too.
Here's an example of what Nova Launcher allows to do:
As you can see, it allows not only to choose which shape to use, but also avoid a shape at all (in "prefer legacy icons").
Here are some links about it:
- https://www.youtube.com/watch?v=5MHFYfXno9c
- https://medium.com/@ianhlake/vectordrawable-adaptive-icons-3fed3d3205b5
The problem
While I know how to create a AdaptiveIconDrawable instance, and I'm aware of the wizard that helps creating one for the current app, I don't get how, given an AdaptiveIconDrawable instance, launchers change the shape.
Not only that, but I remember I saw a launcher or two that allows to not have any shape.
Sadly I can't find any information about this part, maybe because this is a relatively very new feature. There isn't even a keyword for it here on StackOverflow.
What I've tried
I tried reading about adaptive icons, but couldn't find a reference to the receiver side.
I know it has the 2 drawables within it:
- https://developer.android.com/reference/android/graphics/drawable/AdaptiveIconDrawable.html#getBackground()
- https://developer.android.com/reference/android/graphics/drawable/AdaptiveIconDrawable.html#getForeground()
I know, at least, how to get an AdaptiveIconDrawable instance out of a third party app (assuming it has one) :
PackageManager pm = context.getPackageManager();
Intent launchIntentForPackage = pm.getLaunchIntentForPackage(packageName);
String fullPathToActivity = launchIntentForPackage.getComponent().getClassName();
ActivityInfo activityInfo = pm.getActivityInfo(new ComponentName(packageName, fullPathToActivity), 0);
int iconRes = activityInfo.icon;
Drawable drawable = pm.getDrawable(packageName, iconRes, activityInfo.applicationInfo); // will be AdaptiveIconDrawable, if the app has it
The questions
Given a AdaptiveIconDrawable instance, how do you shape it, to be of a circular shape, rectangle, rounded rectangle, tear, and so on?
How do I remove the shape and still have a valid size of the icon (using its foreground drawable in it) ? The official size of an app icon for launchers is 48 dp, while the official ones for AdaptiveIconDrawable inner drawables are 72dp (foreground), 108dp (background). I guess this would mean taking the foreground drawable, resize it somehow, and convert to a bitmap.
In which case exactly is it useful to use
IconCompat.createWithAdaptiveBitmap()
? It was written 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." , but I don't get which cases it's useful for.
EDIT: In order to create a bitmap out of the foreground part of the adaptive icon, while resizing to a proper size, I think this could be a good solution:
val foregroundBitmap = convertDrawableToBitmap(drawable.foreground)
val targetSize = convertDpToPixels(this, ...).toInt()
val scaledBitmap = ThumbnailUtils.extractThumbnail(foregroundBitmap, targetSize, targetSize, ThumbnailUtils.OPTIONS_RECYCLE_INPUT)
fun convertDrawableToBitmap(drawable: Drawable?): Bitmap? {
if (drawable == null)
return null
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bounds = drawable.bounds
val width = if (!bounds.isEmpty) bounds.width() else drawable.intrinsicWidth
val height = if (!bounds.isEmpty) bounds.height() else drawable.intrinsicHeight
val bitmap = Bitmap.createBitmap(if (width <= 0) 1 else width, if (height <= 0) 1 else height,
Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
drawable.bounds = bounds;
return bitmap
}
fun convertDpToPixels(context: Context, dp: Float): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, context.resources.displayMetrics)
Might be able to avoid having 2 bitmaps at the same time, but this is ok I think.
About the creation of a shaped drawable of various types, I'm still not sure how to do it. Only solution I've seen by the answers below is of using a rounded rectangle or a circle, but there are other shapes (for example the tear) that can come to mind.
EDIT: I was told as some point by Google (here) that I should use AdaptiveIconDrawable.getIconMask(), but I wasn't given any further information. However, I've found a nice article about this here.
There's a soooo much simpler way to solve this nowadays using
com.google.android.material.imageview.ShapeableImageView
:That's it. Just set whatever shape you need on the
ShapeableImageView
and the icon will be cropped accordingly.Note: An
AdaptiveIconDrawable
's foreground layer is typically inset by 18dp as per documentation. Depending on your use case, you may want to set thecontentPadding
of the image view to -18dp to offset this.