Drawables – Übersicht

Funktion „Schreiben“ ausprobieren
Jetpack Compose ist das empfohlene UI-Toolkit für Android. Hier erfahren Sie, wie Sie Grafiken in Compose anzeigen lassen.

Wenn Sie statische Bilder in Ihrer Anwendung anzeigen müssen, können Sie die Drawable-Klasse und ihre Unterklassen zum Zeichnen von Formen und Bildern verwenden. Ein Drawable ist eine allgemeine Abstraktion für etwas, das gezeichnet werden kann. Die verschiedenen Unterklassen helfen bei bestimmten Bildszenarien. Du kannst sie erweitern, um deine eigenen Drawable-Objekte zu definieren, die sich auf einzigartige Weise verhalten.

Neben der Verwendung der Klassenkonstruktoren gibt es zwei Möglichkeiten, ein Drawable zu definieren und zu instanziieren:

  • Erweitern Sie eine in Ihrem Projekt gespeicherte Bildressource (eine Bitmapdatei).
  • Baue eine XML-Ressource auf, die die Drawable-Eigenschaften definiert.

Hinweis:Sie können stattdessen auch ein Vektor-Drawable verwenden, das ein Bild mit einer Reihe von Punkten, Linien und Kurven sowie zugehörigen Farbinformationen definiert. So können Vektor-Drawables ohne Qualitätsverlust für verschiedene Größen skaliert werden. Weitere Informationen findest du unter Vektor-Drawables – Übersicht.

Drawables aus Ressourcen-Images erstellen

Sie können Ihrer Anwendung Grafiken hinzufügen, indem Sie auf eine Bilddatei aus Ihren Projektressourcen verweisen. Unterstützte Dateitypen sind PNG (bevorzugt), JPG (akzeptabel) und GIF (nicht empfohlen). App-Symbole, Logos und andere Grafiken, z. B. in Spielen, eignen sich gut für diese Technik.

Wenn Sie eine Image-Ressource verwenden möchten, fügen Sie Ihre Datei dem Verzeichnis res/drawable/ Ihres Projekts hinzu. In Ihrem Projekt können Sie dann in Ihrem Code oder Ihrem XML-Layout auf die Bildressource verweisen. In beiden Fällen wird mithilfe einer Ressourcen-ID auf sie verwiesen. Das ist der Dateiname ohne die Dateityperweiterung. Beziehen Sie my_image.png beispielsweise als my_image.

Hinweis: Bildressourcen, die sich im Verzeichnis res/drawable/ befinden, können während des Build-Prozesses durch das aapt-Tool automatisch mit einer verlustfreien Bildkomprimierung optimiert werden. Beispielsweise kann eine farbige PNG-Datei, die nicht mehr als 256 Farben benötigt, in eine 8-Bit-PNG mit einer Farbpalette konvertiert werden. Dies führt zu einem Bild von gleicher Qualität, das jedoch weniger Arbeitsspeicher erfordert. Daher können sich die in diesem Verzeichnis enthaltenen Image-Binärdateien zum Zeitpunkt der Build-Erstellung ändern. Wenn Sie ein Bild als Bitstream lesen möchten, um es in eine Bitmap umzuwandeln, legen Sie die Bilder stattdessen im Ordner res/raw/ ab, damit sie vom aapt-Tool nicht geändert werden.

Das folgende Code-Snippet zeigt, wie du ein ImageView erstellst, das ein Bild verwendet, das aus einer Drawable-Ressource erstellt wurde, und dem Layout hinzugefügt wird:

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);
}

In anderen Fällen können Sie die Image-Ressource als Drawable-Objekt verarbeiten, wie im folgenden Beispiel gezeigt:

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);

Warnung:Jede eindeutige Ressource in Ihrem Projekt kann nur einen Status beibehalten, unabhängig davon, wie viele verschiedene Objekte Sie instanziieren. Wenn Sie beispielsweise zwei Drawable-Objekte von derselben Bildressource instanziieren und ein Attribut (z. B. Alpha) für ein Objekt ändern, wirkt sich dies auch auf das andere aus. Bei mehreren Instanzen einer Bildressource sollten Sie eine Tween-Animation ausführen, anstatt das Drawable-Objekt direkt zu transformieren.

Das folgende XML-Snippet zeigt, wie eine Drawable-Ressource zu einem ImageView im XML-Layout hinzugefügt wird:

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

Weitere Informationen zur Verwendung von Projektressourcen finden Sie unter Ressourcen und Assets.

Hinweis:Wenn du Bildressourcen als Quelle deiner Drawables verwendest, achte darauf, dass die Bilder die richtige Größe für verschiedene Pixeldichten haben. Wenn die Bilder nicht korrekt sind, werden sie entsprechend skaliert, was zu Artefakten in deinen Drawables führen kann. Weitere Informationen finden Sie unter Verschiedene Pixeldichten unterstützen.

Drawables aus XML-Ressourcen erstellen

Wenn du ein Drawable-Objekt erstellen möchtest, das nicht zuerst von den durch deinen Code oder deine Nutzerinteraktion definierten Variablen abhängt, ist die Definition von Drawable in XML eine gute Option. Auch wenn Sie davon ausgehen, dass Drawable die Eigenschaften während der Interaktion des Nutzers mit Ihrer Anwendung ändert, sollten Sie das Objekt in XML definieren, da Sie die Eigenschaften ändern können, nachdem das Objekt instanziiert wurde.

Nachdem Sie die Drawable in XML definiert haben, speichern Sie die Datei im Verzeichnis res/drawable/ Ihres Projekts. Das folgende Beispiel zeigt den XML-Code, der eine TransitionDrawable-Ressource definiert, die von Drawable übernimmt:

<!-- 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>

Rufen Sie dann das Objekt ab und instanziieren Sie es, indem Sie Resources#getDrawable() aufrufen und die Ressourcen-ID der XML-Datei übergeben. Jede abgeleitete Drawable-Klasse, die die Methode inflate() unterstützt, kann in XML definiert und von Ihrer Anwendung instanziiert werden.

Jede Drawable-Klasse, die XML-Inflation unterstützt, verwendet bestimmte XML-Attribute, die beim Definieren der Objekteigenschaften helfen. Mit dem folgenden Code wird das TransitionDrawable instanziiert und als Inhalt eines ImageView-Objekts festgelegt:

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.

Weitere Informationen zu den unterstützten XML-Attributen finden Sie in den oben aufgeführten Klassen.

Form-Drawables

Ein ShapeDrawable-Objekt kann eine gute Option sein, wenn Sie eine zweidimensionale Grafik dynamisch zeichnen möchten. Sie können einfache Formen programmatisch auf einem ShapeDrawable-Objekt zeichnen und die für Ihre App erforderlichen Stile anwenden.

ShapeDrawable ist eine abgeleitete Klasse von Drawable. Daher können Sie überall dort, wo ein Drawable erwartet wird, ein ShapeDrawable verwenden. Sie können beispielsweise ein ShapeDrawable-Objekt verwenden, um den Hintergrund einer Ansicht festzulegen. Dazu übergeben Sie es an die setBackgroundDrawable()-Methode der Ansicht. Sie können die Form auch als eigene benutzerdefinierte Ansicht zeichnen und sie einem Layout in Ihrer App hinzufügen.

Da ShapeDrawable eine eigene draw()-Methode hat, können Sie eine Unterklasse von View erstellen, die das ShapeDrawable-Objekt während des onDraw()-Ereignisses zeichnet, wie im folgenden Codebeispiel gezeigt:

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);
  }
}

Sie können die Klasse CustomDrawableView im Codebeispiel oben wie jede andere benutzerdefinierte Ansicht verwenden. Sie können ihn beispielsweise programmatisch einer Aktivität in Ihrer Anwendung hinzufügen, wie im folgenden Beispiel gezeigt:

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);
}

Wenn Sie stattdessen die benutzerdefinierte Ansicht im XML-Layout verwenden möchten, muss die CustomDrawableView-Klasse den View(Context, AttributeSet)-Konstruktor überschreiben, der aufgerufen wird, wenn die Klasse aus dem XML-Format aufgebläht wird. Das folgende Beispiel zeigt, wie CustomDrawableView im XML-Layout deklariert wird:

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

Wie bei vielen anderen Drawable-Typen im android.graphics.drawable-Paket können Sie bei der ShapeDrawable-Klasse verschiedene Attribute des Objekts mithilfe öffentlicher Methoden definieren. Beispiele für Eigenschaften, die Sie anpassen können, sind Alphatransparenz, Farbfilter, Dither, Deckkraft und Farbe.

Du kannst mithilfe von XML-Ressourcen auch einfache Drawable-Formen definieren. Weitere Informationen finden Sie unter Form-Drawable im Abschnitt Drawable-Ressourcentypen.

Neun Patch-Drawables

Eine NinePatchDrawable-Grafik ist ein dehnbares Bitmapbild, das Sie als Hintergrund einer Ansicht verwenden können. Android passt die Größe der Grafik automatisch an den Inhalt der Ansicht an. Ein Beispiel für die Verwendung eines NinePatch-Images ist der Hintergrund, der von standardmäßigen Android-Schaltflächen verwendet wird. Schaltflächen müssen so gestreckt werden, dass sie Strings unterschiedlicher Länge unterbringen können. Eine Neun-Patch-Grafik ist ein Standard-PNG-Image mit einem zusätzlichen 1-Pixel-Rahmen. Es muss mit der Erweiterung 9.png im Verzeichnis res/drawable/ Ihres Projekts gespeichert werden.

Verwenden Sie den Rahmen, um die dehnbaren und statischen Bereiche des Bilds zu definieren. Um einen dehnbaren Abschnitt zu kennzeichnen, zeichnen Sie eine (oder mehrere) 1 Pixel breite schwarze Linie im linken und oberen Teil des Rahmens. Die anderen Rahmenpixel sollten vollständig transparent oder weiß sein. Sie können beliebig viele dehnbare Abschnitte haben. Die relative Größe der dehnbaren Abschnitte bleibt gleich, sodass der größte Abschnitt immer der größte bleibt.

Du kannst auch einen optionalen Drawable-Abschnitt des Bildes definieren (d. h. die Abständerlinien). Zeichnen Sie dazu auf der rechten Seite eine Linie und unten eine Linie. Wenn ein View-Objekt die NinePatch-Grafik als Hintergrund festlegt und dann den Ansichtstext angibt, wird es so gestreckt, dass der gesamte Text nur den Bereich einnimmt, der durch die rechte und die untere Zeile (falls vorhanden) gekennzeichnet ist. Wenn die Abstände nicht enthalten sind, verwendet Android die linke und die obere Linie, um diesen gezeichneten Bereich zu definieren.

Zur Verdeutlichung des Unterschieds zwischen den Linien wird durch die linke und die obere Linie definiert, welche Pixel des Bildes repliziert werden dürfen, um das Bild zu dehnen. Die untere und rechte Linie definieren den relativen Bereich innerhalb des Bildes, den der Inhalt der Ansicht einnehmen darf.

Abbildung 1 zeigt ein Beispiel für eine NinePatch-Grafik, die zur Definition einer Schaltfläche verwendet wird:

Bild des dehnbaren Bereichs
und des Innenrands,

Abbildung 1:Beispiel für eine NinePatch-Grafik, die eine Schaltfläche definiert.

Diese NinePatch-Grafik definiert einen dehnbaren Bereich mit der linken und der oberen Linie und den ziehbaren Bereich mit den unteren und rechten Linien. Im oberen Bild kennzeichnen die gepunkteten grauen Linien die Bereiche des Bildes, die repliziert werden, um das Bild zu dehnen. Das rosa Rechteck im unteren Bild gibt den Bereich an, in dem die Inhalte der Ansicht zulässig sind. Wenn der Inhalt nicht in diesen Bereich passt, wird das Bild gestreckt, damit es passt.

Das Draw 9-Patch-Tool bietet eine äußerst praktische Möglichkeit, Ihre NinePatch-Bilder mit einem WYSIWYG-Grafikeditor zu erstellen. Es werden sogar Warnungen ausgegeben, wenn in dem Bereich, den Sie für den dehnbaren Bereich definiert haben, die Gefahr besteht, dass als Ergebnis der Pixelreplikation Zeichenartefakte entstehen.

Der folgende Beispiel-Layout-XML-Code zeigt, wie eine NinePatch-Grafik zu einigen Schaltflächen hinzugefügt wird. Das NinePatch-Image wird in res/drawable/my_button_background.9.png gespeichert.

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

Die Attribute layout_width und layout_height sind auf wrap_content gesetzt, damit die Schaltfläche ordentlich um den Text passt.

Abbildung 2 zeigt die beiden Schaltflächen, die aus dem oben gezeigten XML- und NinePatch-Image gerendert werden. Die Breite und Höhe der Schaltfläche variiert je nach Text. Außerdem wird das Hintergrundbild so gestreckt, dass es Platz einnimmt.

Bild von kleinen Schaltflächen
in normaler Größe,

Abbildung 2:Mit einer XML-Ressource und einer NinePatch-Grafik gerenderte Schaltflächen.

Benutzerdefinierte Drawables

Wenn Sie benutzerdefinierte Zeichnungen erstellen möchten, können Sie dies tun, indem Sie die Drawable-Klasse (oder eine ihrer Unterklassen) erweitern.

Die wichtigste Implementierungsmethode ist draw(Canvas), da damit das Canvas-Objekt bereitgestellt wird, das Sie für die Zeichnungsanleitung verwenden müssen.

Der folgende Code zeigt eine einfache abgeleitete Klasse von Drawable, mit der ein Kreis gezeichnet wird:

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;
    }
}

Anschließend kannst du dein Drawable beliebig hinzufügen, z. B. zu einem ImageView, wie hier gezeigt:

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));

Unter Android 7.0 (API-Level 24) und höher kannst du Instanzen deines benutzerdefinierten Drawables auch mit XML so definieren:

  • Verwendung des voll qualifizierten Klassennamens als XML-Elementnamen. Bei diesem Ansatz muss die benutzerdefinierte Drawable-Klasse eine öffentliche Klasse der obersten Ebene sein:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
    
  • Verwendung von drawable als Name des XML-Tags und Angabe des voll qualifizierten Klassennamens aus dem Klassenattribut. Dieser Ansatz kann sowohl für öffentliche übergeordnete Klassen als auch für öffentliche statische innere Klassen verwendet werden:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />
    

Drawables einfärben

Unter Android 5.0 (API-Level 21) und höher können Sie Bitmaps und neun Patches, die als Alphamasken definiert sind, färben. Sie können sie mit Farbressourcen oder Designattributen färben, die zu Farbressourcen aufgelöst werden (z. B. ?android:attr/colorPrimary). Normalerweise erstellen Sie diese Assets nur einmal und färben sie automatisch ein, damit sie zu Ihrem Design passen.

Mit der Methode setTint() können Sie eine Färbung auf BitmapDrawable-, NinePatchDrawable- oder VectorDrawable-Objekte anwenden. Sie können die Färbung und den Modus in Ihren Layouts auch mit den Attributen android:tint und android:tintMode festlegen.

Auffällige Farben aus einem Bild extrahieren

Die Android Support Library enthält die Klasse Palette, mit der du auffällige Farben aus einem Bild extrahieren kannst. Du kannst deine Drawables als Bitmap laden und an Palette übergeben, um auf die Farben zuzugreifen. Weitere Informationen finden Sie unter Farben mit der Palette API auswählen.