Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do launchers change the shape of an adaptive icon, including removal of background?

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:

enter image description here

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

  1. Given a AdaptiveIconDrawable instance, how do you shape it, to be of a circular shape, rectangle, rounded rectangle, tear, and so on?

  2. 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.

  3. 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.

like image 857
android developer Avatar asked Dec 02 '17 19:12

android developer


People also ask

What is adaptive launcher icon?

Android 8.0 (API level 26) introduces adaptive launcher icons, which can display a variety of shapes across different device models. For example, an adaptive launcher icon can display a circular shape on one OEM device, and display a squircle on another device.

How do I get rid of adaptive icon?

Just go to android > app > src > main > res and delete all the drawable-dpi folders (except for the plain drawable folder), and the mipmap-any folder. Those folders contain the files for the adaptive icon, and without them Android just uses the regular icon.

What is adaptive icon in Android?

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"/>

What are adaptive launcher icons?

Each device OEM provides a mask, which the system then uses to render all adaptive icons with the same shape. Adaptive launcher icons are also used in shortcuts, the Settings app, sharing dialogs, and the overview screen.”

How to change the shape of icons in Nova launcher?

Open Nova Launcher Settings. Go to Look & Feel > Adaptive Icons. Choose the preffered shape of Adaptive Icons. That’s about it. It is a bare minimum, but at least you’re able to adjust the icons to a more likable shape.

What are adaptive icons?

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:

Can I change the look of adaptive icons on my phone?

But, there’s a way to at least change the appearance of Adaptive Icons on your phone. Obviously, this can’t be done using your stock launcher, but if you really can’t stand the current look of Adaptive Icons on your phone, there’s a third-party solution. Nova Launcher is arguably the most popular third-party launcher for Android.


1 Answers

I don't get how, given an AdaptiveIconDrawable instance, launchers change the shape.

Launchers are just apps, so they simply draw the background in the shape they want (or the user selected) and then draw the foreground on top.

I don't have a sample project of my own, but Nick Butcher made a great sample project and series of blog posts: AdaptiveIconPlayground.


Given a AdaptiveIconDrawable instance, how do you shape it, to be of a circular shape, rectangle, rounded rectangle, tear, and so on?

The simplest way is to rasterize the drawable and draw the bitmap using a shader like it is done in Nick's AdaptiveIconView:

private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val background: Bitmap  // ...  background = Bitmap.createBitmap(layerSize, layerSize, Bitmap.Config.ARGB_8888) backgroundPaint.shader = BitmapShader(background, CLAMP, CLAMP)  // < rasterize drawable onto `background` >  // draw desired shape(s) canvas.drawRoundRect(0f, 0f, iconSize.toFloat(), iconSize.toFloat(),                 cornerRadius, cornerRadius, backgroundPaint) 

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.

If you don't want a background, just don't draw it. You're in full control. The size does not really matter, because you usually know how big your icons should be drawn. The documentation states that foreground and background should be 108dp, so you can simply downscale your drawing. If foreground/background use vector graphics, then size really does not matter, as you can just draw them however big you like.

If you rasterize the foreground, then you can do custom drawing as seen above, or choose Canvas#drawBitmap(...), which also offers multiple options to draw a Bitmap, including to pass in a transformation matrix, or simply some bounds.

If you don't rasterize your drawable you can also use drawable.setBounds(x1, y1, x2, y2), where you can set the bounds on where the drawable should draw itself. This should also work.

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.

ShortCutInfo.Builder has a setIcon(Icon icon) method where you need to pass it in. (And the same applies for the compat versions)

It seems that Icon is used to have control over the kind of Bitmap that gets passed in as an icon. Right now I could not find any other usage for Icon. I don't think that you would use this when creating a launcher.


More information reflecting the last comment

Do you wrap the AdaptiveIconDrawable class with your own drawable? I just want to convert it somehow to something I can use, to both an ImageView and a Bitmap, and I wish to control the shape, using all shapes I've shown on the screenshot above. How would I do it?

If you follow the links above you can see a custom AdaptiveIconView that draws the AdaptiveIconDrawable, so doing a custom view is definitely an option, but everything mentioned can be moved just as easily into a custom Drawable, which you then could also use with a basic ImageView.

You can achieve the various different backgrounds by using the methods available on Canvas along with a BitmapShader as shown above, e.g. additionally to drawRoundRect we would have

canvas.drawCircle(centerX, centerY, radius, backgroundPaint) // circle canvas.drawRect(0f, 0f, width, height, backgroundPaint) // rect canvas.drawPath(path, backgroundPaint) // more complex shapes 

To switch between background shapes you could use anything from if/else, over composition, to inheritance, and just draw the shape you like.

like image 133
David Medenjak Avatar answered Sep 18 '22 04:09

David Medenjak