web-dev-qa-db-fra.com

Comment les lanceurs modifient-ils la forme d'une icône adaptative, y compris la suppression de l'arrière-plan?

Contexte

À partir d'Android O, les applications peuvent avoir des icônes adaptatives, qui sont 2 couches de dessinables: le premier plan et un arrière-plan. L'arrière-plan est un masque qui devient une forme du choix du lanceur/utilisateur, tandis que le système d'exploitation a également une forme par défaut.

Voici un exemple de ce que Nova Launcher permet de faire:

 enter image description here

Comme vous pouvez le constater, cela permet non seulement de choisir la forme à utiliser, mais également d'éviter toute forme (dans "Préférez les icônes héritées").

Voici quelques liens à ce sujet:

Le problème

Bien que je sache créer une instance AdaptiveIconDrawable et que je connaisse l’assistant permettant de créer celle-ci pour l’application actuelle, je ne comprends pas comment, dans le cas d’une instance AdaptiveIconDrawable, les lanceurs modifient la forme.

Non seulement cela, mais je me souviens avoir vu un lanceur ou deux qui permet de ne pas avoir de forme.

Malheureusement, je ne trouve aucune information sur cette partie, peut-être parce que c'est une fonctionnalité relativement nouvelle. Il n'y a même pas de mot clé pour cela ici sur StackOverflow.

Ce que j'ai essayé

J'ai essayé de lire sur les icônes adaptatives, mais je n'ai pas trouvé de référence du côté du récepteur.

Je sais qu'il a les 2 tirables dedans:

Je sais au moins comment obtenir une instance AdaptiveIconDrawable d'une application tierce (en supposant qu'elle en ait une):

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

Questions

  1. Dans le cas d'une instance AdaptiveIconDrawable, comment la formez-vous pour qu'elle soit de forme circulaire, rectangle, rectangle arrondi, déchirure, etc.?

  2. Comment puis-je supprimer la forme tout en conservant une taille valide de l'icône (en utilisant son premier plan pouvant être dessiné à l'intérieur)? La taille officielle d'une icône d'application pour les lanceurs est de 48 dp, tandis que les dimensions officielles des objets pouvant être tirés de AdaptiveIconDrawable sont 72 dp (au premier plan) et 108 dp (de fond). J'imagine que cela voudrait dire prendre l’avant-plan comme dessinable, le redimensionner d’une manière ou d’une autre et le convertir en bitmap.

  3. Dans quel cas est-il utile d'utiliser IconCompat.createWithAdaptiveBitmap()? Il a été écrit que "Si vous créez un raccourci dynamique à l'aide d'une bitmap, vous pouvez trouver la IconCompat.createWithAdaptiveBitmap () Support Library 26.0.0-beta2 utile pour garantir que votre bitmap est masqué correctement pour correspondre aux autres icônes adaptatives." , mais je ne comprends pas pour quels cas il est utile.


EDIT: Afin de créer un bitmap hors de la partie au premier plan de l’icône adaptative, tout en redimensionnant à une taille appropriée, je pense que cela pourrait être une bonne 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)

Pourrait peut-être éviter d'avoir 2 bitmaps en même temps, mais ça va, je pense.

À propos de la création d’une forme pouvant être dessinée de différents types, je ne sais toujours pas comment le faire. La seule solution que j’ai vue par les réponses ci-dessous consiste à utiliser un rectangle arrondi ou un cercle, mais il existe d’autres formes (par exemple, la déchirure) qui peuvent venir à l’esprit.

22
android developer

Je ne comprends pas comment, étant donné une instance AdaptiveIconDrawable, les lanceurs modifient la forme.

Les lanceurs ne sont que des applications. Ils dessinent donc l'arrière-plan dans la forme souhaitée (ou l'utilisateur sélectionné), puis le premier plan au premier plan.

Je n'ai pas d'exemple de projet à moi, mais Nick Butcher a créé un excellent exemple de projet et une série d'articles en ligne: AdaptiveIconPlayground .


Dans le cas d'une instance AdaptiveIconDrawable, comment la formez-vous pour qu'elle soit de forme circulaire, rectangle, rectangle arrondi, déchirure, etc.?

Le moyen le plus simple est de pixelliser le dessinable et de dessiner le bitmap utilisant un shader comme cela est fait dans Nick 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)

Comment puis-je supprimer la forme tout en conservant une taille valide de l'icône (en utilisant son premier plan pouvant être dessiné à l'intérieur)? La taille officielle d'une icône d'application pour les lanceurs est de 48 dp, tandis que les dimensions officielles des objets pouvant être tirés de AdaptiveIconDrawable sont 72 dp (au premier plan) et 108 dp (de fond). J'imagine que cela voudrait dire prendre l’avant-plan comme dessinable, le redimensionner d’une manière ou d’une autre et le convertir en bitmap.

Si vous ne voulez pas de fond, ne le dessinez pas. Vous êtes en plein contrôle. La taille n'a pas d'importance, car vous savez généralement quelle taille vos icônes doivent être dessinées. La documentation indique que l'avant-plan et l'arrière-plan doivent être de 108dp, vous pouvez donc simplement réduire votre dessin. Si le premier plan/l’arrière-plan utilise des graphiques vectoriels, alors la taille n’a vraiment pas d’importance, car vous pouvez simplement les dessiner quelle que soit votre taille.

Si vous rasterisez le premier plan, vous pouvez alors faire un dessin personnalisé comme indiqué ci-dessus, ou choisir Canvas#drawBitmap(...), qui offre également plusieurs options pour dessiner un bitmap, notamment pour passer dans une matrice de transformation ou simplement quelques limites.

Si vous ne pixellisez pas votre dessin, vous pouvez également utiliser drawable.setBounds(x1, y1, x2, y2), où vous pouvez définir les limites sur lesquelles le dessinable doit se dessiner. Cela devrait aussi fonctionner.

Dans quel cas est-il utile d'utiliser IconCompat.createWithAdaptiveBitmap ()? Il a été écrit que "Si vous créez un raccourci dynamique à l'aide d'une bitmap, vous pouvez trouver la IconCompat.createWithAdaptiveBitmap () Support Library 26.0.0-beta2 utile pour garantir que votre bitmap est masqué correctement pour correspondre aux autres icônes adaptatives." , mais je ne comprends pas pour quels cas il est utile.

ShortCutInfo.Builder possède une méthode setIcon(Icon icon) où vous devez la transmettre. (Il en va de même pour les versions compatibles)

Il semble que Icon ait l'habitude de contrôler le type de bitmap transmis sous forme d'icône. Pour le moment, je ne trouvais aucune autre utilisation pour Icon. Je ne pense pas que vous utiliseriez cela lors de la création d'un lanceur.


Plus d'informations reflétant le dernier commentaire

Enveloppez-vous la classe AdaptiveIconDrawable avec votre propre dessin? Je veux simplement le convertir en quelque chose que je peux utiliser, à la fois en ImageView et en Bitmap, et je souhaite contrôler la forme, en utilisant toutes les formes que j'ai montrées sur la capture d'écran ci-dessus. Comment pourrais-je le faire?

Si vous suivez les liens ci-dessus, vous pouvez voir une AdaptiveIconView personnalisée qui dessine AdaptiveIconDrawable. Il est donc tout à fait possible de créer une vue personnalisée, mais tout ce qui est mentionné peut être déplacé tout aussi facilement dans un Drawable personnalisé, que vous pourrez également utiliser avec une base. ImageView.

Vous pouvez obtenir les différents arrière-plans en utilisant les méthodes disponibles sur Canvas avec un BitmapShader comme indiqué ci-dessus, par exemple. en plus de drawRoundRect nous aurions

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

Pour basculer entre les formes d'arrière-plan, vous pouvez utiliser n'importe quoi depuis if/else, la composition, l'héritage et simplement dessiner la forme de votre choix.

1
David Medenjak

Les lanceurs ont beaucoup moins de restrictions que les applications, ils peuvent donc utiliser d'autres approches, mais une solution a bien été présentée dans Adaptive Icon Playground de Nick Butcher.

La classe qui vous intéresse probablement est la Vue adaptative des icônes qui rend des versions adaptées de l’icône en créant un raster de chaque calque avec l’arrière-plan en tant que bitmap de canevas, puis en dessinant ces calques sous forme de rectangles arrondis pour mettre en oeuvre le découpage. .

Le référentiel lié sera beaucoup plus informatif et comprendra des exemples sur la façon de transformer le calque pour des effets de mouvement, etc., mais voici le pseudo-code de base pour "adapter une icône" dans une vue d'image:

setIcon() {
    //erase canvas first...
    canvas.setBitmap(background)
    drawable.setBounds(0, 0, layerSize, layerSize)
    drawable.draw(canvas)
}

onDraw() {
    //draw shadow first if needed...
    canvas.drawRoundRect(..., cornerRadius, backgroundPaint)
    canvas.drawRoundRect(..., cornerRadius, foregroundPaint)
}
0
Nick Cardoso