Android ImageView escala una imagen más pequeña al ancho con una altura flexible sin recortar ni distorsionar

79

Preguntados a menudo, nunca respondidos (al menos no de forma reproducible).

Tengo una vista de imagen con una imagen que es más pequeña que la vista. Quiero escalar la imagen al ancho de la pantalla y ajustar la altura de ImageView para reflejar la altura proporcionalmente correcta de la imagen.

<ImageView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
/>

Esto da como resultado la imagen centrada en su tamaño original (más pequeño que el ancho de la pantalla) con márgenes a los lados. No es bueno.

Así que agregué

android:adjustViewBounds="true" 

Mismo efecto, nada bueno. yo añadí

android:scaleType="centerInside"

Mismo efecto, no es bueno. Me cambié centerInsidea fitCenter. Mismo efecto, nada bueno. Me cambié centerInsidea centerCrop.

android:scaleType="centerCrop"

Ahora, finalmente, la imagen se escala al ancho de la pantalla, ¡pero se recorta en la parte superior e inferior! Así que me cambié centerCropa fitXY.

android:scaleType="fitXY"

Ahora la imagen se escala al ancho de la pantalla pero no en el eje y, lo que resulta en una imagen distorsionada .

Eliminar android:adjustViewBounds="true"no tiene ningún efecto. Agregar un android:layout_gravity, como se sugiere en otra parte, nuevamente no tiene ningún efecto.

He probado otras combinaciones, sin éxito. Entonces, por favor, ¿alguien sabe?

¿Cómo se configura el XML de un ImageView para llenar el ancho de la pantalla, escalar una imagen más pequeña para llenar la vista completa, mostrando la imagen con su relación de aspecto sin distorsión ni recorte?

EDITAR: También intenté establecer una altura numérica arbitraria. Esto solo tiene efecto con la centerCropconfiguración. Distorsionará la imagen verticalmente según la altura de la vista.

Mundi
fuente
¿Usted ha intentado android:scaleType="fitCenter"?
Michael Celey
1
@ BrainSlugs83 Tengo y todavía funciona para mí. Además, se planteó la pregunta para determinar qué había intentado el autor de la pregunta. No hay necesidad de sarcasmo, especialmente no siete meses después de su publicación.
Michael Celey
No funciona. -- Vea abajo; Además, lo he probado y puedo verificar que no funciona (si lo ha probado y ve resultados diferentes, analícelo a continuación y muéstrenos lo que estamos haciendo mal; la única conclusión a la que puedo llegar es que no ha entendido bien el pregunta, o no lo he probado).
BrainSlugs83

Respuestas:

110

Resolví esto creando una clase java que incluyes en tu archivo de diseño:

public class DynamicImageView extends ImageView {

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

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        final Drawable d = this.getDrawable();

        if (d != null) {
            // ceil not round - avoid thin vertical gaps along the left/right edges
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            this.setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

Ahora, usa esto agregando tu clase a tu archivo de diseño:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <my.package.name.DynamicImageView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        android:src="@drawable/about_image" />
</RelativeLayout>
Mark Martinsson
fuente
4
+1 Gran solución, funciona perfectamente también para Nexus 7. Y la mejor parte es que también funciona correctamente dentro del Editor de diseño en Eclipse.
Ridcully
Funciona en Galaxy S4. ¿Por qué no debería hacerlo :) THx
Malachiasz
Gracias. Gran solucion Modificar lo suficientemente fácil para hacer lo mismo en función de heightMeasureSpec.
speedynomads
1
Sí pero no ... Esta solución difumina las imágenes. Si utilizo ImageView y fitCenter con altura fija, la imagen no aparece borrosa (pero la altura fija no es una solución para mí). ¿Cómo evitar el difuminado?
Geltrude
Estuve buscando esta solución durante semanas, ¡felicitaciones! Sin embargo, quiero
Soko
36

Tuve el mismo problema que el tuyo. Después de 30 minutos de probar diferentes variaciones, resolví el problema de esta manera. Le da la altura flexible y el ancho ajustado al ancho principal

<ImageView
            android:id="@+id/imageview_company_logo"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:adjustViewBounds="true"
            android:scaleType="fitCenter"
            android:src="@drawable/appicon" />
haydarkarrar
fuente
4
La clave aquí es usar AdjustViewBounds
user3690202
@ user3690202 tienes razón, también resolvió mi problema. Gracias
Parth Patel
22

No existe una solución viable dentro del estándar de diseño XML.

La única forma confiable de reaccionar a un tamaño de imagen dinámico es usar LayoutParams en código.

Decepcionante.

Mundi
fuente
8
  android:scaleType="fitCenter"

Calcule una escala que mantendrá la relación de aspecto original de src, pero también garantizará que src se ajuste completamente dentro de dst. Al menos un eje (X o Y) encajará exactamente. El resultado se centra dentro de dst.

editar:

El problema aquí es que layout_height="wrap_content"no está "permitiendo" que la imagen se expanda. Tendrás que establecer un tamaño para ello, para ese cambio

  android:layout_height="wrap_content"

a

  android:layout_height="100dp"  // or whatever size you want it to be

edit2:

funciona bien:

<ImageView
    android:id="@+id/imageView1"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:scaleType="fitCenter"
    android:src="@drawable/img715945m" />
Budius
fuente
2
Esto da como resultado la primera versión de la imagen: centrada, no expandida horizontalmente, con márgenes en ambos lados. No es bueno.
Mundi
2
Esto no funciona. Da como resultado una imagen con la altura exacta, pero no expandida horizontalmente. No es bueno. También vea mi edición en la pregunta.
Mundi
1
nasa.gov/images/content/715945main__NOB0093_4x3_516-387.jpg La parte importante es que parece ser más pequeña que la pantalla del Nexus 7.
Mundi
2
No es bueno. No sé la altura, es dinámica. Dará lugar a distorsiones impredecibles. La edición en mi pregunta ya cubre esto. Lo siento, ¡pero gracias por tus esfuerzos! ¡Qué vergüenza en Google!
Mundi
1
Sabía que esta es una opción, pero estaba buscando una solución XML . Aún así, debes editar esto en tu respuesta. ¡Gracias!
Mundi
8

Esta es una pequeña adición a la excelente solución de Mark Martinsson.

Si el ancho de su imagen es mayor que su altura, la solución de Mark dejará espacio en la parte superior e inferior de la pantalla.

Lo siguiente corrige esto comparando primero el ancho y el alto: si el ancho de la imagen> = alto, entonces escalará el alto para que coincida con el alto de la pantalla, y luego escalará el ancho para preservar la relación de aspecto. De manera similar, si la altura de la imagen> ancho, escalará el ancho para que coincida con el ancho de la pantalla y luego escalará la altura para conservar la relación de aspecto.

En otras palabras, satisface adecuadamente la definición de scaleType="centerCrop" :

http://developer.android.com/reference/android/widget/ImageView.ScaleType.html

Escale la imagen de manera uniforme (mantenga la relación de aspecto de la imagen) para que ambas dimensiones (ancho y alto) de la imagen sean iguales o mayores que la dimensión correspondiente de la vista (menos relleno).

package com.mypackage;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class FixedCenterCrop extends ImageView
{
    public FixedCenterCrop(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
    {
        final Drawable d = this.getDrawable();

        if(d != null) {
            int height = MeasureSpec.getSize(heightMeasureSpec);
            int width = MeasureSpec.getSize(widthMeasureSpec);

            if(width >= height)
                height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            else
                width = (int) Math.ceil(height * (float) d.getIntrinsicWidth() / d.getIntrinsicHeight());

            this.setMeasuredDimension(width, height);

        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

Esta solución funciona automáticamente en modo vertical u horizontal. Lo hace referencia en su diseño tal como lo hace en la solución de Mark. P.ej:

<com.mypackage.FixedCenterCrop
    android:id="@+id/imgLoginBackground"
    android:src="@drawable/mybackground"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:scaleType="centerCrop" />
Michael Christoff
fuente
3

Me encontré con el mismo problema, pero con una altura fija, el ancho escalado manteniendo la relación de aspecto original de la imagen. Lo resolví mediante un diseño lineal ponderado. Con suerte, puede modificarlo según sus necesidades.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/image"
        android:layout_width="0px"
        android:layout_height="180dip"
        android:layout_weight="1.0"
        android:adjustViewBounds="true"
        android:scaleType="fitStart" />

</LinearLayout>
tbm
fuente
Esta solución funcionó muy bien para mí. Me permitió darle a mi ImageView una altura específica y hacer que ImageView ajustara dinámicamente su ancho para mantener su relación de aspecto.
Lucas
0

Versión de Kotlin de la respuesta de Mark Martinsson :

class DynamicImageView(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val drawable = this.drawable

    if (drawable != null) {
        //Ceil not round - avoid thin vertical gaps along the left/right edges
        val width = View.MeasureSpec.getSize(widthMeasureSpec)
        val height = Math.ceil((width * drawable.intrinsicHeight.toFloat() / drawable.intrinsicWidth).toDouble()).toInt()
        this.setMeasuredDimension(width, height)
    } else {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}
ngu
fuente
0

Una adición más a la solución de Mark Matinsson. Descubrí que algunas de mis imágenes estaban sobreescaladas que otras. Entonces modifiqué la clase para que la imagen se escala en un factor máximo. Si la imagen es demasiado pequeña para escalarla al ancho máximo sin volverse borrosa, deja de escalar más allá del límite máximo.

public class DynamicImageView extends ImageView {

    final int MAX_SCALE_FACTOR = 2;
    public DynamicImageView(final Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        final Drawable d = this.getDrawable();

        if (d != null) {
            // ceil not round - avoid thin vertical gaps along the left/right edges
            int width = View.MeasureSpec.getSize(widthMeasureSpec);
            if (width > (d.getIntrinsicWidth()*MAX_SCALE_FACTOR)) width = d.getIntrinsicWidth()*MAX_SCALE_FACTOR;
            final int height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            this.setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}
azmath
fuente