Android: ¿Cómo estirar una imagen al ancho de la pantalla manteniendo la relación de aspecto?

118

Quiero descargar una imagen (de tamaño desconocido, pero que siempre es aproximadamente cuadrada) y mostrarla de modo que llene la pantalla horizontalmente y se estire verticalmente para mantener la relación de aspecto de la imagen, en cualquier tamaño de pantalla. Aquí está mi código (que no funciona). Estira la imagen horizontalmente, pero no verticalmente, por lo que se aplasta ...

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
fuente
¿Todos los dispositivos Android tienen exactamente la misma relación ancho / alto? De lo contrario, es simplemente imposible escalar una imagen para que se ajuste a todo el ancho / alto mientras se conserva la proporción original ...
NoozNooz42
4
No, no quiero que la imagen llene la pantalla, solo para escalar al ancho de la pantalla, no me importa cuánto de la pantalla ocupe la imagen verticalmente, siempre que la imagen tenga las proporciones correctas.
Fredley
1
Pregunta similar, buena respuesta: stackoverflow.com/questions/4677269/…
Pēteris Caune
1
¿'AdjustViewBounds' no funcionó?
Darpan

Respuestas:

132

Logré esto con una vista personalizada. Establezca layout_width = "fill_parent" y layout_height = "wrap_content", y apúntelo al dibujable apropiado:

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
fuente
12
¡¡Gracias!! ¡Esta debería ser la respuesta correcta! :) Ajusté esto para un poco más de flexibilidad en esta publicación: stackoverflow.com/questions/4677269/… Allí uso un ImageView para que uno pueda configurar el elemento de diseño en el XML o usarlo como ImageView normal en el código para configurar la imagen. ¡Funciona genial!
Patrick Boos
1
¡Genio maldito! que funcionó como un campeón! Gracias, resolvió mi problema con un banner de imagen en la parte superior que no es correcto en todos los tamaños de pantalla. Muchas gracias!
JPM
1
Tenga cuidado con que el logotipo sea nulo (si está descargando contenido de Internet). De lo contrario, esto funciona muy bien, siga la publicación de Patrick para el fragmento XML.
Artem Russakovskii
44
No puedo creer lo difícil que es hacer cosas en Android que son trivialmente fáciles en iOS y Windows Phone.
mxcl
1
Hice algunos cambios, pero en lugar de publicarlos en otro lugar, ¿sería posible convertir esta respuesta en una wiki comunitaria? Mis cambios son todas las cosas que manejan los casos especiales mencionados aquí, así como la capacidad de elegir qué lado se redimensiona mediante la definición del diseño.
Steve Pomeroy
24

Al final, generé las dimensiones manualmente, lo que funciona muy bien:

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
fuente
He estado buscando este tipo de solución para solucionar mi problema. Con la ayuda de este cálculo, logré cargar la imagen con éxito sin reducir la relación de aspecto.
Steve
21

Acabo de leer el código fuente ImageViewy es básicamente imposible sin usar las soluciones de subclases en este hilo. En ImageView.onMeasurellegamos a estas líneas:

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

Donde hy wson las dimensiones de la imagen, y p*es el relleno.

Y entonces:

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

Entonces, si tiene un layout_height="wrap_content", se establecerá widthSize = w + pleft + pright, o en otras palabras, el ancho máximo es igual al ancho de la imagen.

Esto significa que, a menos que establezca un tamaño exacto, las imágenes NUNCA se ampliarán . Considero que esto es un error, pero buena suerte para que Google se dé cuenta o lo solucione. Editar: ¡Comiendo mis propias palabras, envié un informe de error y dicen que se ha solucionado en una versión futura!

Otra solución

Aquí hay otra solución de subclases, pero deberías (en teoría, ¡no la he probado mucho!) Poder usarla en cualquier lugar ImageView. Para usarlo, configure layout_width="match_parent"y layout_height="wrap_content". También es mucho más general que la solución aceptada. Por ejemplo, puede ajustar tanto a la altura como a la anchura.

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
fuente
Envié un informe de error . ¡Mira, ya que se ignora!
Timmmm
3
Aparentemente tengo que comerme mis palabras, ¡dicen que se ha solucionado en una versión futura!
Timmmm
Muchas gracias Tim. Esta debería ser la respuesta correcta final. He buscado durante mucho tiempo y nada es mejor que tu solución.!
2015
¿Qué sucede si necesito establecer maxHeight cuando uso el StretchyImageView anterior? No encontré ninguna solución para eso.
2015
17

La configuración de AdjustViewBounds en verdadero y el uso de un grupo de vista LinearLayout funcionó muy bien para mí. No es necesario realizar una subclase o solicitar 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
fuente
1
... o usando la propiedad XML ImageView - android: adjustViewBounds = "true"
Anatoliy Shuba
¡Perfecto! Esto fue tan fácil y estira la imagen a la perfección. ¿De qué manera esta respuesta no es la correcta? La respuesta con CustomView no funcionó para mí (algún extraño error xml)
user3800924
13

He estado luchando con este problema de una forma u otra durante AGES, gracias, gracias, GRACIAS .... :)

Solo quería señalar que puede obtener una solución generalizable de lo que hizo Bob Lee simplemente extendiendo View y anulando onMeasure. De esa manera, puede usar esto con cualquier elemento de diseño que desee y no se romperá si no hay imagen:

    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
fuente
2
De todas las muchas, muchas soluciones a este problema, esta es la primera que es solo una solución inmediata, donde las otras siempre tienen inconvenientes (como no poder cambiar la imagen en tiempo de ejecución sin volver a escribir el código). ¡Gracias!
MacD
Esto es pura maravilla: he estado luchando con esto durante algún tiempo y todas las soluciones obvias que utilizan los atributos xml nunca funcionaron. Con esto, simplemente podría reemplazar mi ImageView con la vista extendida y ¡todo funcionó como se esperaba!
slott
Esta es absolutamente la mejor solución a este problema.
erlando
Esa es una gran respuesta. Puede usar esta clase en un archivo xml sin preocupaciones como las siguientes: <com.yourpackagename.CardImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: background = "@ drawable / your_photo" />. ¡Excelente!
arniotaki
11

En algunos casos, esta fórmula mágica resuelve maravillosamente el problema.

Para cualquiera que tenga problemas con esto proveniente de otra plataforma, la opción "tamaño y forma para adaptarse" se maneja maravillosamente en Android, pero es difícil de encontrar.

Por lo general, desea esta combinación:

  • padre de coincidencia de ancho,
  • contenido de envoltura de altura,
  • AdjustViewBounds activado (sic)
  • escala fitCenter
  • cropToPadding OFF (sic)

Entonces es automático y sorprendente.

Si eres un desarrollador de iOS, es absolutamente asombroso lo simple que es, en Android, puedes hacer "alturas de celda totalmente dinámicas" en una vista de tabla ... eh, me refiero a ListView. Disfrutar.

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

ingrese la descripción de la imagen aquí

Fattie
fuente
6

Me las arreglé para lograr esto usando solo este código XML. Puede darse el caso de que eclipse no represente la altura para mostrar que se expande para ajustarse; sin embargo, cuando lo ejecuta en un dispositivo, se procesa correctamente y proporciona el resultado deseado. (Bueno, al menos para mi)

<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
fuente
Lo siento, esto no funciona. Escala el ancho, pero centerCrop no mantiene la relación de aspecto.
ricosrealm
1
Después de muchas horas luchando con clases personalizadas extendiendo ImageView (como está escrito en muchas respuestas y artículos) y teniendo una excepción como "No se pudieron encontrar las siguientes clases", eliminé ese código. De repente, noté que un problema en mi ImageView estaba en el atributo de fondo. Lo cambió a srcImageView y se volvió proporcional.
CoolMind
5

Lo hice con estos valores dentro de un LinearLayout:

Scale type: fitStart
Layout gravity: fill_horizontal
Layout height: wrap_content
Layout weight: 1
Layout width: fill_parent
Holduel
fuente
¿Podría dar un ejemplo real? ¿Dónde pongo estos valores en mi archivo xml?
John Smith Opcional
5

Todos están haciendo esto de manera programada, así que pensé que esta respuesta encajaría perfectamente aquí. Este código funcionó para mi en el xml. NO estoy pensando en la proporción todavía, pero aún así quería colocar esta respuesta si pudiera ayudar a alguien.

android:adjustViewBounds="true"

Salud..

Sindri Þór
fuente
3

Una solución muy sencilla es utilizar las funciones proporcionadas por RelativeLayout.

Aquí está el xml que lo hace posible con Android estándar 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>

El truco es que configuras el ImageViewpara llenar la pantalla, pero tiene que estar por encima de los otros diseños. De esta forma logras todo lo que necesitas.

Warpzit
fuente
2

¡Es simple cuestión de configuración adjustViewBounds="true"y scaleType="fitCenter"en el archivo XML para 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á configurado enmatch_parent

NP Kaushik
fuente
1

Está configurando ScaleType en ScaleType.FIT_XY. Según los javadocs , esto estirará la imagen para que se ajuste a toda el área, cambiando la relación de aspecto si es necesario. Eso explicaría el comportamiento que está viendo.

Para obtener el comportamiento que desea ... FIT_CENTER, FIT_START o FIT_END están cerca, pero si la imagen es más estrecha que alta, no comenzará a llenar el ancho. Sin embargo, podría ver cómo se implementan y probablemente debería poder averiguar cómo ajustarlo para su propósito.

Cheryl Simon
fuente
2
Probé FIT_CENTER, pero no los demás. Desafortunadamente, tampoco funcionan, ambos con setAdjustViewBounds establecido en verdadero y falso. ¿Hay alguna forma de obtener el ancho de píxel de la pantalla del dispositivo, así como el ancho / alto de la imagen descargada y configurar manualmente el tamaño?
Fredley
1

ScaleType.CENTER_CROP hará lo que quieras: estirar hasta el ancho completo y escalar la altura en consecuencia. si la altura escalada excede los límites de la pantalla, la imagen se recortará.

P. Melch
fuente
1

Mira, hay una solución mucho más fácil para tu 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);
}
usuario3673209
fuente
1

Puede usar mi StretchableImageView conservando la relación de aspecto (por ancho o por alto) dependiendo del ancho y alto del dibujable:

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
fuente
0

Para mí, android: scaleType = "centerCrop" no resolvió mi problema. De hecho, amplió la imagen mucho más. Así que probé con android: scaleType = "fitXY" y funcionó excelente.

Ricardo
fuente
0

Esto funciona bien según mi requisito

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

Anand Savjani
fuente