Présentation de Drawable

Essayer Compose
Jetpack Compose est le kit d'outils d'interface utilisateur recommandé pour Android. Découvrez comment afficher des éléments graphiques dans Compose.

Lorsque vous devez afficher des images statiques dans votre application, vous pouvez utiliser la classe Drawable et ses sous-classes pour dessiner des formes et des images. Un Drawable est une abstraction générale pour quelque chose qui peut être dessiné. Les différentes sous-classes répondent à des scénarios d'image spécifiques. Vous pouvez les étendre pour définir vos propres objets drawables qui se comportent de manière unique.

En plus d'utiliser les constructeurs de classe, il existe deux façons de définir et d'instancier un Drawable:

  • Gonflez une ressource image (un fichier bitmap) enregistrée dans votre projet.
  • Gonflez une ressource XML qui définit les propriétés drawable.

Remarque:Vous pouvez préférer utiliser un drawable vectoriel, qui définit une image avec un ensemble de points, de lignes et de courbes, ainsi que les informations de couleur associées. Cela permet de mettre à l'échelle les drawables vectoriels de différentes tailles sans perte de qualité. Pour en savoir plus, consultez la présentation des drawables vectoriels.

Créer des drawables à partir d'images de ressources

Vous pouvez ajouter des éléments graphiques à votre application en référençant un fichier image à partir des ressources de votre projet. Les types de fichiers acceptés sont PNG (recommandé), JPG (acceptable) et GIF (déconseillé). Les icônes, les logos et les autres éléments graphiques tels que ceux utilisés dans les jeux sont adaptés à cette technique.

Pour utiliser une ressource image, ajoutez votre fichier au répertoire res/drawable/ de votre projet. Une fois dans le projet, vous pouvez référencer la ressource image à partir de votre code ou de votre mise en page XML. Dans tous les cas, on parle d'ID de ressource, qui correspond au nom du fichier sans l'extension du type de fichier. Par exemple, faites référence à my_image.png sous le nom my_image.

Remarque:Les ressources d'image placées dans le répertoire res/drawable/ peuvent être automatiquement optimisées avec une compression d'image sans perte par l'outil aapt pendant le processus de compilation. Par exemple, un fichier PNG en couleurs réelles qui ne nécessite pas plus de 256 couleurs peut être converti en fichier PNG 8 bits avec une palette de couleurs. Il en résulte une image de qualité égale, mais qui nécessite moins de mémoire. Par conséquent, les binaires d'images placés dans ce répertoire peuvent changer au moment de la compilation. Si vous prévoyez de lire une image sous forme de flux de bits pour la convertir en bitmap, placez plutôt vos images dans le dossier res/raw/, où l'outil aapt ne les modifie pas.

L'extrait de code suivant montre comment créer un ImageView qui utilise une image créée à partir d'une ressource drawable et l'ajoute à la mise en page:

Kotlin

private lateinit var constraintLayout: ConstraintLayout

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Instantiate an ImageView and define its properties
    val i = ImageView(this).apply {
        setImageResource(R.drawable.my_image)
        contentDescription = resources.getString(R.string.my_image_desc)

        // set the ImageView bounds to match the Drawable's dimensions
        adjustViewBounds = true
        layoutParams = ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT)
    }

    // Create a ConstraintLayout in which to add the ImageView
    constraintLayout = ConstraintLayout(this).apply {

        // Add the ImageView to the layout.
        addView(i)
    }

    // Set the layout as the content view.
    setContentView(constraintLayout)
}

Java

ConstraintLayout constraintLayout;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);

  // Create a ConstraintLayout in which to add the ImageView
  constraintLayout = new ConstraintLayout(this);

  // Instantiate an ImageView and define its properties
  ImageView i = new ImageView(this);
  i.setImageResource(R.drawable.my_image);
  i.setContentDescription(getResources().getString(R.string.my_image_desc));

  // set the ImageView bounds to match the Drawable's dimensions
  i.setAdjustViewBounds(true);
  i.setLayoutParams(new ViewGroup.LayoutParams(
          ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT));

  // Add the ImageView to the layout and set the layout as the content view.
  constraintLayout.addView(i);
  setContentView(constraintLayout);
}

Dans d'autres cas, vous pouvez gérer votre ressource d'image comme un objet Drawable, comme illustré dans l'exemple suivant:

Kotlin

val myImage: Drawable = ResourcesCompat.getDrawable(context.resources, R.drawable.my_image, null)

Java

Resources res = context.getResources();
Drawable myImage = ResourcesCompat.getDrawable(res, R.drawable.my_image, null);

Avertissement:Chaque ressource unique de votre projet ne peut conserver qu'un seul état, quel que soit le nombre d'objets différents que vous instanciez pour celle-ci. Par exemple, si vous instanciez deux objets Drawable à partir de la même ressource d'image et modifiez une propriété (telle que la valeur alpha) pour un objet, l'autre est affecté. Lorsque vous traitez plusieurs instances d'une ressource image, au lieu de transformer directement l'objet Drawable, vous devez effectuer une animation d'interpolation.

L'extrait de code XML ci-dessous montre comment ajouter une ressource drawable à un ImageView dans la mise en page XML:

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/my_image"
        android:contentDescription="@string/my_image_desc" />

Pour en savoir plus sur l'utilisation des ressources d'un projet, consultez Ressources et composants.

Remarque:Lorsque vous utilisez des ressources image comme source de vos drawables, assurez-vous que la taille des images est appropriée pour les différentes densités de pixels. Si les images ne sont pas correctes, elles seront mises à l'échelle pour s'adapter, ce qui peut entraîner des artefacts dans vos drawables. Pour en savoir plus, consultez Assurer la compatibilité avec différentes densités de pixels.

Créer des drawables à partir de ressources XML

Si vous souhaitez créer un objet Drawable qui ne dépend pas initialement de variables définies par votre code ou votre interaction utilisateur, définir Drawable en XML est une bonne option. Même si vous prévoyez que votre Drawable change de propriétés lors de l'interaction de l'utilisateur avec votre application, vous devez envisager de définir l'objet en XML, car vous pouvez modifier les propriétés après l'instanciation de l'objet.

Après avoir défini votre Drawable en XML, enregistrez le fichier dans le répertoire res/drawable/ de votre projet. L'exemple suivant montre le code XML qui définit une ressource TransitionDrawable, qui hérite de Drawable:

<!-- res/drawable/expand_collapse.xml -->
<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/image_expand"/>
    <item android:drawable="@drawable/image_collapse"/>
</transition>

Ensuite, récupérez et instanciez l'objet en appelant Resources#getDrawable() et en transmettant l'ID de ressource de votre fichier XML. Toute sous-classe Drawable compatible avec la méthode inflate() peut être définie en XML et instanciée par votre application.

Chaque classe drawable compatible avec le gonflage XML utilise des attributs XML spécifiques qui permettent de définir les propriétés de l'objet. Le code suivant instancie TransitionDrawable et le définit en tant que contenu d'un objet ImageView:

Kotlin

val transition= ResourcesCompat.getDrawable(
        context.resources,
        R.drawable.expand_collapse,
        null
) as TransitionDrawable

val image: ImageView = findViewById(R.id.toggle_image)
image.setImageDrawable(transition)

// Description of the initial state that the drawable represents.
image.contentDescription = resources.getString(R.string.collapsed)

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000)

// After the transition is complete, change the image's content description
// to reflect the new state.

Java

Resources res = context.getResources();
TransitionDrawable transition =
    (TransitionDrawable) ResourcesCompat.getDrawable(res, R.drawable.expand_collapse, null);

ImageView image = (ImageView) findViewById(R.id.toggle_image);
image.setImageDrawable(transition);

// Description of the initial state that the drawable represents.
image.setContentDescription(getResources().getString(R.string.collapsed));

// Then you can call the TransitionDrawable object's methods.
transition.startTransition(1000);

// After the transition is complete, change the image's content description
// to reflect the new state.

Pour en savoir plus sur les attributs XML compatibles, reportez-vous aux classes répertoriées ci-dessus.

Drawables géométriques

Un objet ShapeDrawable peut être une bonne option lorsque vous souhaitez dessiner un graphique en deux dimensions de manière dynamique. Vous pouvez dessiner des formes primitives par programmation sur un objet ShapeDrawable et appliquer les styles dont votre application a besoin.

ShapeDrawable est une sous-classe de Drawable. Pour cette raison, vous pouvez utiliser un ShapeDrawable chaque fois qu'un Drawable est attendu. Par exemple, vous pouvez utiliser un objet ShapeDrawable pour définir l'arrière-plan d'une vue en le transmettant à la méthode setBackgroundDrawable() de la vue. Vous pouvez également dessiner votre forme dans sa propre vue personnalisée et l'ajouter à une mise en page de votre application.

Comme ShapeDrawable possède sa propre méthode draw(), vous pouvez créer une sous-classe de View qui dessine l'objet ShapeDrawable lors de l'événement onDraw(), comme illustré dans l'exemple de code suivant:

Kotlin

class CustomDrawableView(context: Context) : View(context) {
    private val drawable: ShapeDrawable = run {
        val x = 10
        val y = 10
        val width = 300
        val height = 50
        contentDescription = context.resources.getString(R.string.my_view_desc)

        ShapeDrawable(OvalShape()).apply {
            // If the color isn't set, the shape uses black as the default.
            paint.color = 0xff74AC23.toInt()
            // If the bounds aren't set, the shape can't be drawn.
            setBounds(x, y, x + width, y + height)
        }
    }

    override fun onDraw(canvas: Canvas) {
        drawable.draw(canvas)
    }
}

Java

public class CustomDrawableView extends View {
  private ShapeDrawable drawable;

  public CustomDrawableView(Context context) {
    super(context);

    int x = 10;
    int y = 10;
    int width = 300;
    int height = 50;
    setContentDescription(context.getResources().getString(
            R.string.my_view_desc));

    drawable = new ShapeDrawable(new OvalShape());
    // If the color isn't set, the shape uses black as the default.
    drawable.getPaint().setColor(0xff74AC23);
    // If the bounds aren't set, the shape can't be drawn.
    drawable.setBounds(x, y, x + width, y + height);
  }

  protected void onDraw(Canvas canvas) {
    drawable.draw(canvas);
  }
}

Vous pouvez utiliser la classe CustomDrawableView dans l'exemple de code ci-dessus comme vous utiliseriez toute autre vue personnalisée. Par exemple, vous pouvez l'ajouter de manière programmatique à une activité dans votre application, comme illustré dans l'exemple suivant:

Kotlin

private lateinit var customDrawableView: CustomDrawableView

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    customDrawableView = CustomDrawableView(this)

    setContentView(customDrawableView)
}

Java

CustomDrawableView customDrawableView;

protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  customDrawableView = new CustomDrawableView(this);

  setContentView(customDrawableView);
}

Si vous souhaitez utiliser la vue personnalisée dans la mise en page XML, la classe CustomDrawableView doit remplacer le constructeur View(Context, AttributeSet), qui est appelé lorsque la classe est gonflée à partir du XML. L'exemple suivant montre comment déclarer l'élément CustomDrawableView dans la mise en page XML:

<com.example.shapedrawable.CustomDrawableView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        />

Comme de nombreux autres types de drawables du package android.graphics.drawable, la classe ShapeDrawable vous permet de définir différentes propriétés de l'objet à l'aide de méthodes publiques. Voici quelques exemples de propriétés que vous pouvez ajuster : transparence alpha, filtre de couleur, profondeur, opacité et couleur.

Vous pouvez également définir des formes drawables primitives à l'aide de ressources XML. Pour en savoir plus, consultez la section Drawable géométrique dans la section Types de ressources drawables.

Drawables NinePatch

Un graphique NinePatchDrawable est une image bitmap extensible que vous pouvez utiliser comme arrière-plan d'une vue. Android redimensionne automatiquement l'image pour l'adapter au contenu de l'affichage. L'arrière-plan utilisé par les boutons Android standards est un exemple d'utilisation d'une image NinePatch. Les boutons doivent s'étirer pour s'adapter à des chaînes de longueurs différentes. Un graphique NinePatch est une image PNG standard qui comprend une bordure supplémentaire d'un pixel. Il doit être enregistré avec l'extension 9.png dans le répertoire res/drawable/ de votre projet.

Utilisez la bordure pour définir les zones étirables et statiques de l'image. Pour indiquer une section étirable, dessinez une ou plusieurs lignes noires d'un pixel de large dans la partie gauche et la partie supérieure de la bordure (les autres pixels de bordure doivent être entièrement transparents ou blancs). Vous pouvez avoir autant de sections étirables que vous le souhaitez. La taille relative des sections étirables reste la même. Par conséquent, la plus grande section reste toujours la plus grande.

Vous pouvez également définir une section drawable facultative de l'image (en fait, les lignes de marge intérieure) en traçant une ligne à droite et une ligne en bas. Si un objet View définit l'image NinePatch comme arrière-plan, puis spécifie le texte de la vue, il s'étire de sorte que tout le texte n'occupe que la zone désignée par les lignes de droite et de bas (le cas échéant). Si les lignes de marge intérieure ne sont pas incluses, Android utilise les lignes de gauche et du haut pour définir cette zone drawable.

Pour clarifier la différence entre les lignes, les lignes de gauche et du haut définissent les pixels de l'image qui peuvent être répliqués afin d'étirer l'image. Les lignes inférieure et droite définissent la zone relative dans l'image que le contenu de la vue peut occuper.

La figure 1 présente un exemple de graphique NinePatch utilisé pour définir un bouton:

Image de la zone étirable
et de la zone de remplissage

Figure 1:Exemple d'image NinePatch définissant un bouton

Ce graphique NinePatch définit une zone étirable avec les lignes gauche et supérieure, et la zone drawable avec les lignes inférieure et droite. Dans l'image du haut, les lignes grises en pointillés identifient les zones de l'image qui sont répliquées afin d'étirer l'image. Le rectangle rose de l'image du bas identifie la région dans laquelle le contenu de la vue est autorisé. Si le contenu ne rentre pas dans cette zone, l'image est étirée.

L'outil Draw 9-patch est très pratique pour créer vos images NinePatch à l'aide d'un éditeur graphique WYSIWYG. Il génère même des avertissements si la région que vous avez définie pour la zone étirable risque de produire des artefacts de dessin en raison de la réplication des pixels.

L'exemple de code XML de mise en page suivant montre comment ajouter un graphique NinePatch à quelques boutons. L'image NinePatch est enregistrée dans res/drawable/my_button_background.9.png.

<Button android:id="@+id/tiny"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerInParent="true"
        android:text="Tiny"
        android:textSize="8sp"
        android:background="@drawable/my_button_background"/>

<Button android:id="@+id/big"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerInParent="true"
        android:text="Biiiiiiig text!"
        android:textSize="30sp"
        android:background="@drawable/my_button_background"/>

Notez que les attributs layout_width et layout_height sont définis sur wrap_content pour que le bouton s'adapte parfaitement au texte.

La figure 2 montre les deux boutons affichés à partir du fichier XML et de l'image NinePatch ci-dessus. Notez que la largeur et la hauteur du bouton varient en fonction du texte, et que l'image de fond s'étire pour s'y adapter.

Image de boutons minuscules et
de taille normale

Figure 2:Boutons affichés à l'aide d'une ressource XML et d'un graphique NinePatch

Drawables personnalisés

Lorsque vous souhaitez créer des dessins personnalisés, vous pouvez étendre la classe Drawable (ou l'une de ses sous-classes).

La méthode la plus importante à implémenter est draw(Canvas), car elle fournit l'objet Canvas que vous devez utiliser pour fournir vos instructions de dessin.

Le code suivant montre une sous-classe simple de Drawable qui trace un cercle:

Kotlin

class MyDrawable : Drawable() {
    private val redPaint: Paint = Paint().apply { setARGB(255, 255, 0, 0) }

    override fun draw(canvas: Canvas) {
        // Get the drawable's bounds
        val width: Int = bounds.width()
        val height: Int = bounds.height()
        val radius: Float = Math.min(width, height).toFloat() / 2f

        // Draw a red circle in the center
        canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, redPaint)
    }

    override fun setAlpha(alpha: Int) {
        // This method is required
    }

    override fun setColorFilter(colorFilter: ColorFilter?) {
        // This method is required
    }

    override fun getOpacity(): Int =
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        PixelFormat.OPAQUE
}

Java

public class MyDrawable extends Drawable {
    private final Paint redPaint;

    public MyDrawable() {
        // Set up color and text size
        redPaint = new Paint();
        redPaint.setARGB(255, 255, 0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        // Get the drawable's bounds
        int width = getBounds().width();
        int height = getBounds().height();
        float radius = Math.min(width, height) / 2;

        // Draw a red circle in the center
        canvas.drawCircle(width/2, height/2, radius, redPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        // This method is required
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        // This method is required
    }

    @Override
    public int getOpacity() {
        // Must be PixelFormat.UNKNOWN, TRANSLUCENT, TRANSPARENT, or OPAQUE
        return PixelFormat.OPAQUE;
    }
}

Vous pouvez ensuite ajouter votre drawable où vous le souhaitez, par exemple à un ImageView, comme indiqué ci-dessous:

Kotlin

val myDrawing = MyDrawable()
val image: ImageView = findViewById(R.id.imageView)
image.setImageDrawable(myDrawing)
image.contentDescription = resources.getString(R.string.my_image_desc)

Java

MyDrawable mydrawing = new MyDrawable();
ImageView image = findViewById(R.id.imageView);
image.setImageDrawable(mydrawing);
image.setContentDescription(getResources().getString(R.string.my_image_desc));

Sur Android 7.0 (niveau d'API 24) ou version ultérieure, vous pouvez également définir des instances de votre drawable personnalisé au format XML comme suit:

  • Utiliser le nom de classe complet comme nom d'élément XML. Pour cette approche, la classe drawable personnalisée doit être une classe publique de premier niveau :
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
    
  • En utilisant drawable comme nom de balise XML et en spécifiant le nom de classe complet à partir de l'attribut de classe. Cette approche peut être utilisée à la fois pour les classes de niveau supérieur publiques et les classes internes statiques publiques :
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />
    

Ajouter une teinte aux drawables

Avec Android 5.0 (niveau d'API 21) ou version ultérieure, vous pouvez teinter les bitmaps et les fichiers Nine-Patch définis en tant que masques alpha. Vous pouvez les teinter avec des ressources de couleur ou des attributs de thème qui se résolvent en ressources de couleur (par exemple, ?android:attr/colorPrimary). En règle générale, vous ne créez ces éléments qu'une seule fois et vous les colorez automatiquement pour qu'ils correspondent à votre thème.

Vous pouvez appliquer une teinte aux objets BitmapDrawable, NinePatchDrawable ou VectorDrawable à l'aide de la méthode setTint(). Vous pouvez également définir la couleur et le mode de teinte dans vos mises en page avec les attributs android:tint et android:tintMode.

Extraire les couleurs importantes d'une image

La bibliothèque Android Support inclut la classe Palette, qui vous permet d'extraire les couleurs importantes d'une image. Vous pouvez charger vos drawables en tant que Bitmap et les transmettre à Palette pour accéder à ses couleurs. Pour en savoir plus, consultez la section Sélectionner des couleurs avec l'API Palette.