Visão geral de drawables

Quando você precisa exibir imagens estáticas no seu app, é possível usar a classe Drawable e as subclasses relacionadas para desenhar formas e imagens. Um Drawable é uma abstração geral para algo que pode ser desenhado. As diversas subclasses ajudam com cenários de imagem específicos, e é possível estendê-las para definir os próprios objetos drawable que se comportam de maneiras únicas.

Há duas maneiras de definir e instanciar um Drawable, além de usar os construtores da classe:

  • Inflar um recurso de imagem (arquivo de bitmap) salvo no projeto.
  • Inflar um recurso XML que define as propriedades do drawable.

Observação: talvez você prefira usar um drawable vetorial, que define uma imagem com um conjunto de pontos, linhas e curvas, junto às informações de cor associadas. Isso permite que drawables vetoriais sejam dimensionados para tamanhos diferentes sem perda de qualidade. Para mais informações, consulte Visão geral de drawables vetoriais.

Criar drawables a partir de imagens de recursos

É possível adicionar gráficos ao app referenciando um arquivo de imagem dos recursos do projeto. Os tipos de arquivo compatíveis são PNG (preferencial), JPG (aceitável) e GIF (não recomendado). Ícones do app, logotipos e outros elementos gráficos, como aqueles usados em jogos, são adequados para essa técnica.

Para usar um recurso de imagem, adicione seu arquivo ao diretório res/drawable/ do projeto. Quando você estiver no projeto, poderá referenciar o recurso de imagem do seu código ou layout XML. De qualquer forma, ele é referenciado por meio de um código de recurso, que é o nome do arquivo sem a extensão de tipo. Por exemplo, consulte my_image.png como my_image.

Observação: recursos de imagem colocados no diretório res/drawable/ podem ser otimizados automaticamente com a compactação de imagem sem perdas pela ferramenta aapt durante o processo de compilação. Por exemplo, um PNG de cor verdadeira que não exija mais de 256 cores pode ser convertido em um PNG de 8 bits com uma paleta de cores. Isso resulta em uma imagem com a mesma qualidade, mas que requer menos memória. Como resultado, os binários de imagem colocados nesse diretório podem mudar durante a compilação. Se você pretende ler uma imagem como bitstream para convertê-la em um bitmap, coloque suas imagens na pasta res/raw/, onde a ferramenta aapt não as modifica.

O snippet de código a seguir mostra como criar uma ImageView que usa uma imagem criada a partir de um recurso drawable e a adiciona ao 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);
    }
    

Em outros casos, processe seu recurso de imagem como um objeto Drawable, como mostrado no exemplo a seguir:

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

Observação: cada recurso exclusivo no seu projeto pode manter apenas um estado, independentemente de quantos objetos distintos você instancie para ele. Por exemplo, se você instanciar dois objetos Drawable do mesmo recurso de imagem e mudar uma propriedade (como a Alfa) de um dos objetos, o outro também será afetado. Ao lidar com várias instâncias de um recurso de imagem, em vez de transformar diretamente o objeto Drawable, faça uma animação de interpolação.

O snippet de XML abaixo mostra como adicionar um recurso drawable a um ImageView no layout XML:

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

Para mais informações sobre o uso de recursos do projeto, consulte Recursos.

Observação: ao usar recursos de imagem como fonte dos seus drawables, verifique se as imagens têm o tamanho adequado para várias densidades de pixel. Se as imagens não estiverem corretas, elas serão ampliadas, o que pode gerar artefatos nos seus drawables. Para ver mais informações, leia Compatibilidade com densidades de pixel diferentes.

Criar drawables a partir de recursos XML

Se você quiser criar um objeto Drawable que não depende inicialmente de variáveis definidas pelo seu código ou interação do usuário, a definição de Drawable em XML será uma boa opção. Mesmo que você espere que Drawable mude as propriedades durante a interação do usuário com seu app, considere a possibilidade de definir o objeto em XML, já que você poderá modificar as propriedades depois que elas forem instanciadas.

Depois de definir seu Drawable em XML, salve o arquivo no diretório res/drawable/ do projeto. O exemplo a seguir mostra o XML que define um recurso TransitionDrawable, que herda 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>
    

Em seguida, recupere e instancie o objeto chamando Resources.getDrawable() e transmitindo o código do recurso do seu arquivo XML. Qualquer subclasse Drawable compatível com o método inflate() pode ser definida em XML e instanciada pelo seu app. Cada classe de drawable compatível com a inflação XML usa atributos XML específicos que ajudam a definir as propriedades do objeto. O código a seguir instancia o TransitionDrawable e o define como o conteúdo de um objeto 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.
    

Para mais informações sobre os atributos XML compatíveis, consulte as classes listadas acima.

Drawables de formato

Um objeto ShapeDrawable pode ser uma boa opção quando você quer desenhar dinamicamente um gráfico bidimensional. É possível desenhar formas básicas de maneira programática em um objeto ShapeDrawable e aplicar os estilos de que seu app precisa.

ShapeDrawable é uma subclasse de Drawable. Por isso, você pode usar um ShapeDrawable onde quer que um Drawable seja esperado. Por exemplo, é possível usar um objeto ShapeDrawable para definir o plano de fundo de uma visualização passando-o para o método setBackgroundDrawable() da visualização. Você também pode desenhar sua forma como a própria visualização personalizada e adicioná-la a um layout no app.

Como ShapeDrawable tem o próprio método draw(), é possível criar uma subclasse de View que desenhe o objeto ShapeDrawable durante o evento onDraw(), conforme mostrado no exemplo de código a seguir:

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

É possível usar a classe CustomDrawableView na amostra de código acima, como seria feito com qualquer outra visualização personalizada. Por exemplo, você pode adicioná-la de forma programática a uma atividade no app, como mostrado no exemplo a seguir:

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 você quiser usar a visualização personalizada no layout XML, a classe CustomDrawableView precisará modificar o construtor View(Context, AttributeSet), que é chamado quando a classe é inflada do XML. O exemplo a seguir mostra como declarar a CustomDrawableView no layout XML:

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

A classe ShapeDrawable, como muitos outros tipos de drawable no pacote android.graphics.drawable, permite definir várias propriedades do objeto por meio de métodos públicos. Alguns exemplos de propriedades que você pode usar incluem transparência Alfa, filtro de cor, pontilhamento, opacidade e cor.

Também é possível definir formas básicas de drawable por meio de recursos XML. Para mais informações, consulte Drawable de formato em Recursos drawables.

Drawables NinePatch

Um gráfico NinePatchDrawable é uma imagem bitmap esticável que pode ser usada como plano de fundo de uma visualização. O Android redimensiona automaticamente o gráfico para acomodar o conteúdo da visualização. Um exemplo de uso de uma imagem NinePatch é o plano de fundo usado pelos botões padrão do Android. Os botões precisam ser esticados para acomodar strings de vários tamanhos. Um gráfico NinePatch é uma imagem PNG padrão que inclui uma borda extra de 1 pixel. Ele precisa ser salvo com a extensão 9.png no diretório res/drawable/ do projeto.

Use a borda para definir as áreas esticáveis e estáticas da imagem. Indique uma seção esticável desenhando uma ou mais linhas pretas com 1 pixel de largura nas partes esquerda e superior da borda (os outros pixels de borda devem ser totalmente transparentes ou brancos). Você poderá usar quantas seções esticáveis quiser. O tamanho relativo das seções esticáveis não muda. Assim, a seção mais extensa será sempre a maior.

Também é possível definir uma seção de drawable opcional da imagem (linhas de preenchimento) desenhando uma linha à direita e outra na parte inferior. Se um objeto View definir o gráfico NinePatch como plano de fundo e especificar o texto da visualização, ele se esticará para que todo o texto ocupe somente a área designada pelas linhas direita e inferior (se forem incluídas). Se as linhas de preenchimento não forem incluídas, o Android usará as linhas esquerda e superior para definir essa área do drawable.

Para esclarecer a diferença, as linhas esquerda e superior definem quais pixels da imagem podem ser replicados para esticar a imagem. As linhas inferior e direita definem a área relativa dentro da imagem que o conteúdo da visualização pode ocupar.

A Figura 1 mostra um exemplo de gráfico NinePatch usado para definir um botão:

Imagem da área esticável e da caixa de preenchimento

Figura 1: exemplo de um gráfico NinePatch que define um botão.

Esse gráfico NinePatch define uma área esticável com as linhas superior e esquerda, e a área do drawable com as linhas inferior e direita. Na imagem superior, as linhas cinza pontilhadas identificam as regiões que são replicadas para esticar a imagem. O retângulo rosa na imagem inferior identifica a região em que o conteúdo da visualização é permitido. Se o conteúdo não couber nessa região, a imagem será esticada para ajustá-lo.

A ferramenta Draw 9-patch oferece uma maneira muito prática de criar suas imagens NinePatch por meio de um editor gráfico WYSIWYG. Ele também gerará alertas se a região que você definiu para a área esticável estiver em risco de produzir artefatos de desenho como resultado da replicação de pixels.

A amostra de XML de layout a seguir demonstra como adicionar um gráfico NinePatch a alguns botões. A imagem do NinePatch é salva em 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"/>
    

Observe que os atributos layout_width e layout_height são definidos como wrap_content para que o botão se encaixe perfeitamente ao redor do texto.

A Figura 2 mostra os dois botões renderizados a partir do XML e da imagem do NinePatch mostrada acima. Observe como a largura e a altura do botão variam de acordo com o texto, e a imagem de plano de fundo é esticada para acomodá-lo.

Imagem de botões pequenos e de tamanho normal

Figura 2: botões renderizados por meio de um recurso XML e de um gráfico NinePatch.

Drawables personalizados

Quando você quiser criar alguns desenhos personalizados, estenda a classe Drawable (ou qualquer uma das subclasses relacionadas).

O método mais importante a ser implementado é draw(Canvas), porque ele disponibiliza o objeto Canvas necessário para oferecer suas instruções de desenho.

O código a seguir mostra uma subclasse simples de Drawable que desenha um círculo:

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

Então, você poderá adicionar o drawable onde quiser. Por exemplo, a uma ImageView, como mostrado aqui:

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

No Android 7.0 (API nível 24) e versões posteriores, também é possível definir instâncias do drawable personalizado com XML das seguintes maneiras:

  • Use o nome de classe totalmente qualificado como o nome do elemento XML. Para essa abordagem, a classe de drawable personalizado precisa ser pública de nível superior:
        <com.myapp.MyDrawable xmlns:android="http://schemas.android.com/apk/res/android"
            android:color="#ffff0000" />
        
  • Use drawable como o nome da tag XML e especifique o nome de classe totalmente qualificado a partir do atributo da classe. Essa abordagem pode ser usada para classes públicas de nível superior e classes internas estáticas públicas:
        <drawable xmlns:android="http://schemas.android.com/apk/res/android"
            class="com.myapp.MyTopLevelClass$MyDrawable"
            android:color="#ffff0000" />
        

Adicionar tonalidade a drawables

Com o Android 5.0 (API nível 21) e versões posteriores, é possível colorir os bitmaps e os nine-patches definidos como máscaras Alfa. Para fazer isso, use recursos de cor ou atributos de tema que indiquem esses recursos, como ?android:attr/colorPrimary. Em geral, você deve criar esses recursos apenas uma vez e colori-los automaticamente para corresponder ao seu tema.

Você pode aplicar uma tonalidade aos objetos BitmapDrawable, NinePatchDrawable ou VectorDrawable com o método setTint(). Também é possível definir a cor e o modo da tonalidade nos seus layouts com os atributos android:tint e android:tintMode.

Extrair cores em destaque de uma imagem

A Biblioteca de Suporte do Android inclui a classe Palette, que permite extrair as cores em destaque de uma imagem. Carregue seus drawables como um Bitmap e transmita-o para Palette para acessar as cores dele. Para mais informações, leia Como selecionar cores com a API Palette.