Panoramica dei disegni

Prova la modalità Scrivi
Jetpack Compose è il toolkit dell'interfaccia utente consigliato per Android. Scopri come visualizzare le immagini in Compose.

Quando devi visualizzare immagini statiche nella tua app, puoi utilizzare la classe Drawable e le relative sottoclassi per disegnare forme e immagini. Drawable è un'astrazione generale di qualcosa che può essere disegnato. Le varie sottoclassi sono utili per scenari di immagini specifici e puoi estenderli per definire i tuoi oggetti disegnabili che si comportano in modi unici.

Oltre a utilizzare i costruttori delle classi, esistono due modi per definire e creare un'istanza di Drawable:

  • Aumenta il volume di una risorsa immagine (un file bitmap) salvata nel progetto.
  • Aumenta il valore di una risorsa XML che definisce le proprietà disegnabili.

Nota: è preferibile utilizzare una traccia vettoriale, che definisce un'immagine con un insieme di punti, linee e curve, insieme alle informazioni sul colore associate. Ciò consente di scalare le dimensioni di disegno vettoriali senza perdere la qualità. Per ulteriori informazioni, consulta la panoramica di Vectordrawables.

Creazione di elementi disegnabili dalle immagini delle risorse

Puoi aggiungere elementi grafici all'app facendo riferimento a un file immagine dalle risorse del progetto. I tipi di file supportati sono PNG (opzione preferita), JPG (accettabile) e GIF (sconsigliato). Icone, loghi e altri elementi grafici delle app, come quelli utilizzati nei giochi, sono adatti a questa tecnica.

Per utilizzare una risorsa immagine, aggiungi il file alla directory res/drawable/ del progetto. Una volta avviato il progetto, puoi fare riferimento alla risorsa immagine dal codice o dal layout XML. In ogni caso, viene denominato un ID risorsa, ovvero il nome del file senza estensione. Ad esempio, fai riferimento a my_image.png come my_image.

Nota: le risorse immagine inserite nella directory res/drawable/ possono essere ottimizzate automaticamente con la compressione delle immagini senza perdita di dati dallo strumento aapt durante il processo di compilazione. Ad esempio, un PNG a colori reali che non richiede più di 256 colori può essere convertito in un PNG a 8 bit con una tavolozza dei colori. Il risultato è un'immagine di uguale qualità, ma che richiede meno memoria. Di conseguenza, i file binari delle immagini posizionati in questa directory possono cambiare al momento della creazione. Se prevedi di leggere un'immagine come bitstream per convertirla in bitmap, inserisci le immagini nella cartella res/raw/, dove lo strumento aapt non le modifica.

Lo snippet di codice riportato di seguito mostra come creare un elemento ImageView che utilizza un'immagine creata da una risorsa disegnabile e la aggiunge al layout:

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 altri casi, è consigliabile gestire la risorsa immagine come un oggetto Drawable, come mostrato nell'esempio seguente:

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

Avviso: ogni risorsa univoca nel progetto può mantenere un solo stato, indipendentemente da quanti oggetti diversi hai fornito. Ad esempio, se crei un'istanza di due oggetti Drawable dalla stessa risorsa immagine e modifichi una proprietà (ad esempio la versione alpha) per un oggetto, questa operazione influisce anche sull'altro. Quando hai a che fare con più istanze di una risorsa immagine, invece di trasformare direttamente l'oggetto Drawable devi eseguire un'animazione tra due elementi.

Lo snippet XML riportato di seguito mostra come aggiungere una risorsa disegnabile a ImageView nel layout XML:

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

Per saperne di più sull'utilizzo delle risorse di progetto, consulta Risorse e asset.

Nota: se utilizzi risorse immagine come origine dei disegni, assicurati che le immagini abbiano le dimensioni appropriate per le varie densità di pixel. Se le immagini non sono corrette, verranno ridimensionate per adattarle, il che può causare l'artefattura dei disegni. Per ulteriori informazioni, consulta Supportare diverse densità di pixel.

Creazione diDrawable da risorse XML

Se vuoi creare un oggetto Drawable, che inizialmente non dipende da variabili definite dal codice o dall'interazione dell'utente, è preferibile definire Drawable in XML. Anche se prevedi che Drawable modificherà le proprie proprietà durante l'interazione dell'utente con la tua app, ti consigliamo di definire l'oggetto in XML, dal momento che puoi modificare le proprietà dopo aver creato un'istanza dell'oggetto.

Dopo aver definito Drawable in XML, salva il file nella directory res/drawable/ del tuo progetto. L'esempio seguente mostra il codice XML che definisce una risorsa TransitionDrawable, che eredita da 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>

Quindi, recupera e crea un'istanza dell'oggetto chiamando Resources#getDrawable() e passando l'ID risorsa del tuo file XML. Qualsiasi sottoclasse Drawable che supporta il metodo inflate() può essere definita in XML e creata un'istanza dalla tua app.

Ogni classe disegnabile che supporta l'inflazione XML utilizza attributi XML specifici che aiutano a definire le proprietà dell'oggetto. Il seguente codice crea un'istanza TransitionDrawable e lo imposta come contenuto di un oggetto 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.

Per ulteriori informazioni sugli attributi XML supportati, consulta le classi elencate sopra.

Disegni di forme

Un oggetto ShapeDrawable può essere una buona opzione per disegnare dinamicamente una grafica bidimensionale. Puoi disegnare forme primitive a livello di programmazione su un oggetto ShapeDrawable e applicare gli stili necessari per la tua app.

ShapeDrawable è una sottoclasse di Drawable. Per questo motivo, puoi utilizzare ShapeDrawable ovunque sia previsto un Drawable. Ad esempio, puoi utilizzare un oggetto ShapeDrawable per impostare lo sfondo di una vista passandolo al metodo setBackgroundDrawable() della vista. Puoi anche tracciare la forma con una vista personalizzata e aggiungerla a un layout.

Poiché ShapeDrawable ha il proprio metodo draw(), puoi creare una sottoclasse View che disegna l'oggetto ShapeDrawable durante l'evento onDraw(), come mostrato nel seguente esempio di codice:

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

Puoi utilizzare la classe CustomDrawableView nell'esempio di codice riportato sopra, come faresti con qualsiasi altra visualizzazione personalizzata. Ad esempio, puoi aggiungerla in modo programmatico a un'attività nella tua app, come mostrato nell'esempio seguente:

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

Se preferisci utilizzare la visualizzazione personalizzata nel layout XML, la classe CustomDrawableView deve sostituire il costruttore View(Context, AttributeSet), che viene chiamato quando la classe viene gonfiata dal file XML. L'esempio seguente mostra come dichiarare CustomDrawableView nel layout XML:

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

La classe ShapeDrawable, come molti altri tipi disegnabili nel pacchetto android.graphics.drawable, consente di definire varie proprietà dell'oggetto utilizzando metodi pubblici. Alcune proprietà di esempio che potresti modificare sono trasparenza alfa, filtro colore, dithering, opacità e colore.

Puoi anche definire forme disegnabili primitive utilizzando le risorse XML. Per ulteriori informazioni, consulta Forma disegnabile in Tipi di risorse disegnabili.

Disegnabili NinePatch

Un grafico NinePatchDrawable è un'immagine bitmap estensibile che puoi utilizzare come sfondo di una visualizzazione. Android ridimensiona automaticamente la grafica per adattarla ai contenuti della visualizzazione. Un esempio di utilizzo di un'immagine NinePatch è lo sfondo utilizzato dai pulsanti standard di Android: i pulsanti devono allungarsi per adattarsi a stringhe di varie lunghezze. Un'immagine NinePatch è un'immagine PNG standard che include un bordo aggiuntivo di 1 pixel. Deve essere salvato con l'estensione 9.png nella directory res/drawable/ del tuo progetto.

Utilizza il bordo per definire le aree estensibili e statiche dell'immagine. Per indicare una sezione estensibile, disegna una o più linee nere larghe da 1 pixel nella parte sinistra e superiore del bordo (gli altri pixel del bordo devono essere completamente trasparenti o bianchi). Puoi avere tutte le sezioni estensibili che vuoi. La dimensione relativa delle sezioni estensibili rimane invariata, quindi la sezione più grande rimane sempre la più grande.

Puoi anche definire una sezione disegnabile facoltativa dell'immagine (in pratica, le linee di spaziatura interna) disegnando una linea sulla destra e una sulla parte inferiore. Se un oggetto View imposta la grafica NinePatch come sfondo e poi specifica il testo della visualizzazione, si estende in modo che tutto il testo occupi solo l'area indicata dalle linee a destra e in fondo (se inclusa). Se le linee di spaziatura interna non sono incluse, Android utilizza le righe a sinistra e superiore per definire questa area disegnabile.

Per chiarire la differenza tra le righe, le righe a sinistra e in alto definiscono quali pixel dell'immagine possono essere replicati in modo da allungare l'immagine. Le righe inferiore e destra definiscono l'area relativa all'interno dell'immagine che i contenuti della vista possono occupare.

La Figura 1 mostra un esempio di grafica NinePatch utilizzata per definire un pulsante:

Immagine di area estensibile
e spaziatura interna

Figura 1: esempio di grafica NinePatch che definisce un pulsante

L'immagine di NinePatch definisce un'area estensibile con le linee superiore e sinistra e l'area di disegno con le linee in basso e a destra. Nell'immagine in alto, le linee grigie tratteggiate identificano le aree dell'immagine che vengono replicate in modo da allungare l'immagine. Il rettangolo rosa nell'immagine in basso identifica l'area in cui sono consentiti i contenuti. Se i contenuti non si adattano a questa area, l'immagine viene allungata per adattarle.

Lo strumento Disegna 9-patch offre un modo estremamente pratico per creare le tue immagini NinePatch, utilizzando un editor grafico WYSIWYG. Genera anche avvisi se l'area definita per l'area allungabile rischia di produrre artefatti di disegno a seguito della replica dei pixel.

Il seguente file XML di layout di esempio mostra come aggiungere un'immagine NinePatch a un paio di pulsanti. L'immagine di NinePatch è salvata in 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"/>

Tieni presente che gli attributi layout_width e layout_height sono impostati su wrap_content per fare in modo che il pulsante si adatti in tutto il testo.

La Figura 2 mostra i due pulsanti di cui viene eseguito il rendering dall'immagine XML e da NinePatch mostrata sopra. Osserva come la larghezza e l'altezza del pulsante variano con il testo e come l'immagine di sfondo si allunga per adattarla.

Immagine di pulsanti piccoli
e di dimensioni normali

Figura 2: pulsanti visualizzati con una risorsa XML e un'immagine NinePatch

Disegnabili personalizzati

Quando vuoi creare disegni personalizzati, puoi farlo estendendo la classe Drawable (o una delle sue sottoclassi).

Il metodo più importante da implementare è draw(Canvas) perché fornisce l'oggetto Canvas che devi usare per fornire le istruzioni di disegno.

Il seguente codice mostra una semplice sottoclasse di Drawable che disegna un cerchio:

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

Quindi puoi aggiungere il disegno dove vuoi, ad esempio a ImageView, come mostrato qui:

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

Su Android 7.0 (livello API 24) e versioni successive, puoi anche definire le istanze del disegno personalizzato con XML nei seguenti modi:

  • Utilizzo del nome completo della classe come nome dell'elemento XML. Per questo approccio, la classe disegnabile personalizzata deve essere una classe pubblica di primo livello:
    <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="#ffff0000" />
    
  • Utilizzare drawable come nome del tag XML e specificare il nome completo della classe dall'attributo class. Questo approccio può essere utilizzato sia per le classi di primo livello pubbliche sia per le classi interne statiche pubbliche:
    <drawable xmlns:android="http://schemas.android.com/apk/res/android"
        class="com.myapp.MyTopLevelClass$MyDrawable"
        android:color="#ffff0000" />
    

Aggiungi tinta ai disegni

Con Android 5.0 (livello API 21) e versioni successive, puoi colorare bitmap e nove patch definite come maschere alfa. Puoi colorarli con risorse di colore o attributi del tema che si risolvono con le risorse di colore (ad esempio, ?android:attr/colorPrimary). Di solito, questi asset vengono creati una sola volta e colorati automaticamente in base al tema scelto.

Puoi applicare una tinta agli oggetti BitmapDrawable, NinePatchDrawable o VectorDrawable con il metodo setTint(). Puoi anche impostare il colore e la modalità di tinta nei layout con gli attributi android:tint e android:tintMode.

Estrae colori in evidenza da un'immagine

La libreria di supporto Android include la classe Palette, che consente di estrarre colori in evidenza da un'immagine. Puoi caricare i disegni come Bitmap e passarli a Palette per accedere ai suoi colori. Per ulteriori informazioni, consulta Selezionare i colori con l'API Palette.