Android: como esticar uma imagem para a largura da tela, mantendo a proporção da imagem?

118

Quero baixar uma imagem (de tamanho desconhecido, mas que é sempre quase quadrada) e exibi-la de forma que preencha a tela horizontalmente e estique-a verticalmente para manter a proporção da imagem, em qualquer tamanho de tela. Aqui está meu código (não funcional). Estica a imagem horizontalmente, mas não verticalmente, por isso é comprimida ...

ImageView mainImageView = new ImageView(context);
    mainImageView.setImageBitmap(mainImage); //downloaded from server
    mainImageView.setScaleType(ScaleType.FIT_XY);
    //mainImageView.setAdjustViewBounds(true); 
    //with this line enabled, just scales image down
    addView(mainImageView,new LinearLayout.LayoutParams( 
            LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
Fredley
fonte
todos os dispositivos Android têm exatamente a mesma proporção largura / altura? Do contrário, é simplesmente impossível dimensionar uma imagem para caber em toda a largura / altura, preservando a proporção original ...
NoozNooz42
4
Não, eu não quero que a imagem preencha a tela, apenas escalar para a largura da tela, não me importo quanto da tela a imagem ocupa verticalmente, desde que a imagem esteja nas proporções corretas.
Fredley
1
Pergunta semelhante, boa resposta: stackoverflow.com/questions/4677269/…
Pēteris Caune
1
'AdjustViewBounds' não funcionou?
Darpan

Respostas:

132

Eu fiz isso com uma visão personalizada. Defina layout_width = "fill_parent" e layout_height = "wrap_content" e aponte para o drawable apropriado:

public class Banner extends View {

  private final Drawable logo;

  public Banner(Context context) {
    super(context);
    logo = context.getResources().getDrawable(R.drawable.banner);
    setBackgroundDrawable(logo);
  }

  public Banner(Context context, AttributeSet attrs) {
    super(context, attrs);
    logo = context.getResources().getDrawable(R.drawable.banner);
    setBackgroundDrawable(logo);
  }

  public Banner(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    logo = context.getResources().getDrawable(R.drawable.banner);
    setBackgroundDrawable(logo);
  }

  @Override protected void onMeasure(int widthMeasureSpec,
      int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);
    int height = width * logo.getIntrinsicHeight() / logo.getIntrinsicWidth();
    setMeasuredDimension(width, height);
  }
}
Bob Lee
fonte
12
Obrigado!! Esta deve ser a resposta correta! :) Eu ajustei isso para um pouco mais de flexibilidade neste post: stackoverflow.com/questions/4677269/… Lá eu uso um ImageView para que se possa definir o drawable no XML ou como um ImageView normal no código para definir a imagem. Funciona bem!
Patrick Boos
1
Gênio doido! que funcionou como um campeão! Obrigado, ele resolveu meu problema com um banner de imagem na parte superior incorreto em todos os tamanhos de tela! Muito obrigado!
JPM
1
Cuidado com o logotipo sendo nulo (se você estiver baixando conteúdo da Internet). Caso contrário, isso funciona muito bem, siga a postagem de Patrick para o snippet XML.
Artem Russakovskii
44
Não posso acreditar como é difícil fazer coisas no Android que são trivialmente fáceis no iOS e no Windows Phone.
mxcl
1
Fiz algumas alterações, mas em vez de publicá-las em outro lugar, seria possível transformar esta resposta em um wiki da comunidade? Minhas mudanças são todas as coisas que tratam dos casos especiais mencionados aqui, bem como a capacidade de escolher qual lado será redimensionado por meio da definição do layout.
Steve Pomeroy
24

No final, gerei as dimensões manualmente, o que funciona muito bem:

DisplayMetrics dm = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
int height = width * mainImage.getHeight() / mainImage.getWidth(); //mainImage is the Bitmap I'm drawing
addView(mainImageView,new LinearLayout.LayoutParams( 
        width, height));
Fredley
fonte
Tenho procurado esse tipo de solução para corrigir meu problema. Com a ajuda desse cálculo, consegui carregar a imagem com sucesso sem reduzir a proporção.
Steve
21

Acabei de ler o código-fonte para ImageViewe é basicamente impossível sem usar as soluções de subclasse neste segmento. Em ImageView.onMeasurechegarmos a estas linhas:

        // Get the max possible width given our constraints
        widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);

        // Get the max possible height given our constraints
        heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);

Onde he westão as dimensões da imagem, e p*é o preenchimento.

E depois:

private int resolveAdjustedSize(int desiredSize, int maxSize,
                               int measureSpec) {
    ...
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            /* Parent says we can be as big as we want. Just don't be larger
               than max size imposed on ourselves.
            */
            result = Math.min(desiredSize, maxSize);

Então se você tem um layout_height="wrap_content"ele vai definir widthSize = w + pleft + pright, ou seja, a largura máxima é igual à largura da imagem.

Isso significa que, a menos que você defina um tamanho exato, as imagens NUNCA serão ampliadas . Eu considero isso um bug, mas boa sorte para que o Google o perceba ou corrija. Edit: comendo minhas próprias palavras, enviei um relatório de bug e eles dizem que foi corrigido em uma versão futura!

Outra solução

Aqui está outra solução alternativa com subclasse, mas você deve (em teoria, eu realmente não testei muito!) Ser capaz de usá-la em qualquer lugar ImageView. Para usá-lo layout_width="match_parent", defina elayout_height="wrap_content" . É também muito mais geral do que a solução aceita. Por exemplo, você pode ajustar à altura, bem como ajustar à largura.

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;

// This works around the issue described here: http://stackoverflow.com/a/12675430/265521
public class StretchyImageView extends ImageView
{

    public StretchyImageView(Context context)
    {
        super(context);
    }

    public StretchyImageView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    public StretchyImageView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        // Call super() so that resolveUri() is called.
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // If there's no drawable we can just use the result from super.
        if (getDrawable() == null)
            return;

        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        int w = getDrawable().getIntrinsicWidth();
        int h = getDrawable().getIntrinsicHeight();
        if (w <= 0)
            w = 1;
        if (h <= 0)
            h = 1;

        // Desired aspect ratio of the view's contents (not including padding)
        float desiredAspect = (float) w / (float) h;

        // We are allowed to change the view's width
        boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;

        // We are allowed to change the view's height
        boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

        int pleft = getPaddingLeft();
        int pright = getPaddingRight();
        int ptop = getPaddingTop();
        int pbottom = getPaddingBottom();

        // Get the sizes that ImageView decided on.
        int widthSize = getMeasuredWidth();
        int heightSize = getMeasuredHeight();

        if (resizeWidth && !resizeHeight)
        {
            // Resize the width to the height, maintaining aspect ratio.
            int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright;
            setMeasuredDimension(newWidth, heightSize);
        }
        else if (resizeHeight && !resizeWidth)
        {
            int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
            setMeasuredDimension(widthSize, newHeight);
        }
    }
}
Timmmm
fonte
Enviei um relatório de bug . Observe, pois é ignorado!
Timmmm 01 de
3
Aparentemente, tenho que engolir minhas palavras - dizem que foi consertado em uma versão futura!
Timmmm
Muito obrigado Tim. Esta deve ser a resposta certa final. Procurei muito tempo e nada melhor do que a sua solução.!
tainy
e se eu precisar definir o maxHeight ao usar o StretchyImageView acima. Não encontrei solução para isso.
tainy
17

Configurar o AdjustViewBounds como true e usar um grupo de visualização LinearLayout funcionou muito bem para mim. Não há necessidade de criar subclasses ou pedir métricas de dispositivo:

//NOTE: "this" is a subclass of LinearLayout
ImageView splashImageView = new ImageView(context);
splashImageView.setImageResource(R.drawable.splash);
splashImageView.setAdjustViewBounds(true);
addView(splashImageView);
Matt Stoker
fonte
1
... ou usando a propriedade ImageView XML - android: AdjustViewBounds = "true"
Anatoliy Shuba
Perfeito! Isso foi tão fácil e estica a imagem perfeita. Essa resposta não é a correta? A resposta com o CustomView não funcionou para mim (algum erro xml estranho)
user3800924
13

Tenho lutado com esse problema de uma forma ou de outra por IDADES, obrigado, obrigado, obrigado .... :)

Eu só queria salientar que você pode obter uma solução generalizável a partir do que Bob Lee fez apenas estendendo View e substituindo onMeasure. Dessa forma, você pode usar isso com qualquer drawable que desejar e ele não quebrará se não houver imagem:

    public class CardImageView extends View {
        public CardImageView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
        }

        public CardImageView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        public CardImageView(Context context) {
            super(context);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            Drawable bg = getBackground();
            if (bg != null) {
                int width = MeasureSpec.getSize(widthMeasureSpec);
                int height = width * bg.getIntrinsicHeight() / bg.getIntrinsicWidth();
                setMeasuredDimension(width,height);
            }
            else {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
Duhrer
fonte
2
De todas as muitas, muitas soluções para este problema, esta é a primeira que é apenas uma solução drop-in, onde as outras sempre têm desvantagens (como não ser capaz de alterar a imagem em tempo de execução sem uma reescrita do código). Obrigado!
MacD
Isso é simplesmente incrível - tenho lutado com isso há algum tempo e todas as soluções óbvias usando os atributos xml nunca funcionaram. Com isso eu pude apenas substituir meu ImageView pela visão estendida e tudo funcionou como esperado!
slott
Esta é absolutamente a melhor solução para este problema.
erlando
Essa é uma ótima resposta. Você pode usar esta classe em arquivo xml sem preocupações como: <com.yourpackagename.CardImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: background = "@ drawable / your_photo" />. Ótimo!
arniotaki
11

Em alguns casos, essa fórmula mágica resolve perfeitamente o problema.

Para quem está lutando com isso vindo de outra plataforma, o "tamanho e forma para caber" é tratada perfeitamente no Android, mas é difícil de encontrar.

Você normalmente deseja esta combinação:

  • largura correspondente ao pai,
  • conteúdo de quebra de altura,
  • AdjustViewBounds ligado (sic)
  • escala fitCenter
  • cropToPadding OFF (sic)

Então é automático e incrível.

Se você é um desenvolvedor iOS, é absolutamente incrível como, no Android, você pode fazer "alturas de células totalmente dinâmicas" em uma exibição de tabela ... err, quero dizer ListView. Aproveitar.

<com.parse.ParseImageView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/post_image"
    android:src="@drawable/icon_192"
    android:layout_margin="0dp"
    android:cropToPadding="false"
    android:adjustViewBounds="true"
    android:scaleType="fitCenter"
    android:background="#eff2eb"/>

insira a descrição da imagem aqui

Fattie
fonte
6

Consegui fazer isso usando apenas este código XML. Pode ser que o eclipse não renderize a altura para mostrá-la se expandindo para caber; entretanto, quando você realmente executa isso em um dispositivo, ele renderiza corretamente e fornece o resultado desejado. (Bem, pelo menos para mim)

<FrameLayout
     android:layout_width="match_parent"
     android:layout_height="wrap_content">

     <ImageView
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:adjustViewBounds="true"
          android:scaleType="centerCrop"
          android:src="@drawable/whatever" />
</FrameLayout>
NeilDA
fonte
Desculpe, isso não funciona. Ele dimensiona a largura, mas centerCrop não mantém a proporção da imagem.
ricosrealm
1
Depois de muitas horas lutando com classes personalizadas que estendem ImageView (conforme escrito em muitas respostas e artigos) e tendo uma exceção como "As seguintes classes não puderam ser encontradas", removi esse código. De repente, percebi que um problema no meu ImageView estava no atributo background! Alterado para srce ImageView tornou-se proporcional.
CoolMind
5

Fiz isso com estes valores em um LinearLayout:

Scale type: fitStart
Layout gravity: fill_horizontal
Layout height: wrap_content
Layout weight: 1
Layout width: fill_parent
Holduel
fonte
Você poderia dar um exemplo real? Onde coloco esses valores no meu arquivo xml?
John Smith Opcional
5

Todos estão fazendo isso de maneira programática, então achei que essa resposta se encaixaria perfeitamente aqui. Este código funcionou para mim no xml. Ainda NÃO estou pensando na proporção, mas ainda assim gostaria de colocar esta resposta se isso ajudasse alguém.

android:adjustViewBounds="true"

Felicidades..

Sindri Þór
fonte
3

Uma solução muito simples é apenas usar os recursos fornecidos por RelativeLayout .

Aqui está o xml que torna isso possível com o Android padrão Views:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fillViewport="true">

    <RelativeLayout 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <LinearLayout
            android:id="@+id/button_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:layout_alignParentBottom="true"
            >
            <Button
                android:text="button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <Button
                android:text="button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
            <Button
                android:text="button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"/>
        </LinearLayout>
        <ImageView 
            android:src="@drawable/cat"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:adjustViewBounds="true"
            android:scaleType="centerCrop"
            android:layout_above="@id/button_container"/>
    </RelativeLayout>
</ScrollView>

O truque é definir o ImageViewpara preencher a tela, mas deve estar acima dos outros layouts. Desta forma, você consegue tudo o que precisa.

Warpzit
fonte
2

É simples questão de configurar adjustViewBounds="true"e scaleType="fitCenter"no arquivo XML para o ImageView!

<ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/image"

        android:adjustViewBounds="true"
        android:scaleType="fitCenter"
        />

Nota: layout_widthestá definido paramatch_parent

Kaushik NP
fonte
1

Você está definindo ScaleType como ScaleType.FIT_XY. De acordo com os javadocs , isso esticará a imagem para caber em toda a área, alterando a proporção da imagem se necessário. Isso explicaria o comportamento que você está vendo.

Para obter o comportamento que você deseja ... FIT_CENTER, FIT_START ou FIT_END estão próximos, mas se a imagem for mais estreita do que alta, ela não começará a preencher a largura. Você pode ver como eles são implementados e provavelmente será capaz de descobrir como ajustá-los para seu propósito.

Cheryl Simon
fonte
2
Eu tentei FIT_CENTER, mas não os outros. Infelizmente, eles também não funcionam, com setAdjustViewBounds definido como true e false. Existe alguma maneira de obter a largura em pixels da tela do dispositivo, bem como a largura / altura da imagem baixada e definir manualmente o tamanho?
Fredley
1

ScaleType.CENTER_CROP fará o que você quiser: esticar até a largura total e dimensionar a altura de acordo. se a altura dimensionada exceder os limites da tela, a imagem será cortada.

P.Melch
fonte
1

Veja, há uma solução muito mais fácil para o seu problema:

ImageView imageView;

protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.your_layout);
    imageView =(ImageView)findViewById(R.id.your_imageView);
    Bitmap imageBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.your_image);
    Point screenSize = new Point();
    getWindowManager().getDefaultDisplay().getSize(screenSize);
    Bitmap temp = Bitmap.createBitmap(screenSize.x, screenSize.x, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(temp);
    canvas.drawBitmap(imageBitmap,null, new Rect(0,0,screenSize.x,screenSize.x), null);
    imageView.setImageBitmap(temp);
}
user3673209
fonte
1

Você pode usar meu StretchableImageView preservando a proporção (por largura ou altura) dependendo da largura e altura do drawable:

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;

public class StretchableImageView extends ImageView{

    public StretchableImageView(Context context) {
        super(context);
    }

    public StretchableImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public StretchableImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if(getDrawable()!=null){
            if(getDrawable().getIntrinsicWidth()>=getDrawable().getIntrinsicHeight()){
                int width = MeasureSpec.getSize(widthMeasureSpec);
                int height = width * getDrawable().getIntrinsicHeight()
                            / getDrawable().getIntrinsicWidth();
                setMeasuredDimension(width, height);
            }else{
                int height = MeasureSpec.getSize(heightMeasureSpec);
                int width = height * getDrawable().getIntrinsicWidth()
                            / getDrawable().getIntrinsicHeight();
                setMeasuredDimension(width, height);
            }
        }
    }
}
Valerybodak
fonte
0

Para mim, o android: scaleType = "centerCrop" não resolveu meu problema. Na verdade, expandiu a imagem muito mais. Tentei com android: scaleType = "fitXY" e funcionou perfeitamente.

Ricardo
fonte
0

Isso está funcionando bem de acordo com minha exigência

<ImageView android:id="@+id/imgIssue" android:layout_width="fill_parent" android:layout_height="wrap_content" android:adjustViewBounds="true" android:scaleType="fitXY"/>

Anand Savjani
fonte