Android ImageView skaliert kleinere Bilder mit flexibler Höhe auf Breite, ohne zu beschneiden oder zu verzerren

79

Oft gefragt, nie beantwortet (zumindest nicht reproduzierbar).

Ich habe eine Bildansicht mit einem Bild, das kleiner als die Ansicht ist. Ich möchte das Bild auf die Breite des Bildschirms skalieren und die Höhe der Bildansicht anpassen, um die proportional korrekte Höhe des Bildes wiederzugeben.

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

Dies führt dazu, dass das Bild in seiner ursprünglichen Größe (kleiner als die Bildschirmbreite) mit seitlichen Rändern zentriert wird. Nicht gut.

Also habe ich hinzugefügt

android:adjustViewBounds="true" 

Gleicher Effekt, nicht gut. Ich fügte hinzu

android:scaleType="centerInside"

Gleicher Effekt, nicht gut. Ich wechselte centerInsidezu fitCenter. Gleicher Effekt, nicht gut. Ich wechselte centerInsidezu centerCrop.

android:scaleType="centerCrop"

Jetzt wird das Bild endlich auf die Breite des Bildschirms skaliert - aber oben und unten beschnitten ! Also wechselte ich centerCropzu fitXY.

android:scaleType="fitXY"

Jetzt wird das Bild auf die Breite des Bildschirms skaliert, jedoch nicht auf der y-Achse, was zu einem verzerrten Bild führt.

Das Entfernen android:adjustViewBounds="true"hat keine Auswirkung. Das Hinzufügen von android:layout_gravity, wie an anderer Stelle vorgeschlagen, hat wiederum keine Auswirkung.

Ich habe andere Kombinationen ausprobiert - ohne Erfolg. Also, bitte weiß jemand:

Wie richten Sie das XML einer ImageView so ein, dass es die Breite des Bildschirms ausfüllt, skalieren Sie ein kleineres Bild, um die gesamte Ansicht auszufüllen, und zeigen Sie das Bild mit seinem Seitenverhältnis ohne Verzerrung oder Zuschneiden an?

EDIT: Ich habe auch versucht, eine beliebige numerische Höhe einzustellen. Dies wirkt sich nur auf die centerCropEinstellung aus. Das Bild wird entsprechend der Ansichtshöhe vertikal verzerrt.

Mundi
quelle
Hast du es versucht android:scaleType="fitCenter"?
Michael Celey
1
@ BrainSlugs83 Ich habe und es funktioniert immer noch für mich. Außerdem wurde die Frage gestellt, um festzustellen, was der Fragesteller versucht hatte. Es gibt keine Notwendigkeit für die Snarkiness, besonders nicht sieben Monate nachdem es veröffentlicht wurde.
Michael Celey
Es funktioniert nicht. -- Siehe unten; Außerdem habe ich es versucht und kann überprüfen, ob es nicht funktioniert (wenn Sie es versucht haben und unterschiedliche Ergebnisse sehen, diskutieren Sie bitte unten und zeigen Sie uns, was wir falsch machen - die einzige Schlussfolgerung, zu der ich kommen kann, ist, dass Sie das falsch verstanden haben Frage oder habe es nicht versucht).
BrainSlugs83

Antworten:

110

Ich habe dies gelöst, indem ich eine Java-Klasse erstellt habe, die Sie in Ihre Layout-Datei aufnehmen:

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

Jetzt verwenden Sie dies, indem Sie Ihre Klasse zu Ihrer Layout-Datei hinzufügen:

<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
quelle
4
+1 Tolle Lösung, funktioniert auch perfekt für Nexus 7. Und das Beste daran ist, dass dies auch im Layout-Editor in Eclipse korrekt funktioniert.
Ridcully
Funktioniert auf dem Galaxy S4. Warum sollte es nicht :) THx
Malachiasz
Vielen Dank. Tolle Lösung. Einfach genug zu ändern, um dasselbe basierend auf der heightMeasureSpec zu tun.
Speedynomads
1
Ja, aber nicht ... Diese Lösung verwischt die Bilder. Wenn ich ImageView und fitCenter mit fester Höhe verwende, wird das Bild nicht unscharf (aber feste Höhe ist für mich keine Lösung). Wie vermeide ich ein Verwischen?
Geltrude
Ich habe wochenlang nach dieser Lösung gesucht, ein dickes Lob! Ich möchte zwar ein Bild hochskalieren, aber das funktioniert perfekt
Soko
36

Ich hatte das gleiche Problem wie deins. Nachdem ich 30 Minuten lang verschiedene Variationen ausprobiert hatte, löste ich das Problem auf diese Weise. Sie erhalten die flexible Höhe und Breite, die an die übergeordnete Breite angepasst sind

<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
quelle
4
Der Schlüssel hier ist die Verwendung von adjustViewBounds
user3690202
@ user3690202 Sie haben Recht, es hat auch mein Problem gelöst. Danke
Parth Patel
22

Innerhalb des XML-Layoutstandards gibt es keine praktikable Lösung.

Die einzige zuverlässige Möglichkeit, auf eine dynamische Bildgröße zu reagieren, ist die Verwendung LayoutParamsin Code.

Enttäuschend.

Mundi
quelle
8
  android:scaleType="fitCenter"

Berechnen Sie eine Skala, die das ursprüngliche src-Seitenverhältnis beibehält, aber auch sicherstellt, dass src vollständig in dst passt. Mindestens eine Achse (X oder Y) passt genau. Das Ergebnis ist innerhalb von dst zentriert.

bearbeiten:

Das Problem hierbei ist, layout_height="wrap_content"dass das Bild nicht "erweitert" werden kann. Für diese Änderung müssen Sie eine Größe festlegen

  android:layout_height="wrap_content"

zu

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

edit2:

funktioniert gut:

<ImageView
    android:id="@+id/imageView1"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:scaleType="fitCenter"
    android:src="@drawable/img715945m" />
Budius
quelle
2
Dies führt zur ersten Version des Bildes: zentriert, nicht horizontal erweitert, mit Rändern auf beiden Seiten. Nicht gut.
Mundi
2
Das funktioniert nicht. Das Ergebnis ist ein Bild mit der exakten Höhe, das jedoch nicht horizontal erweitert wird. Nicht gut. Siehe auch meine Bearbeitung in der Frage.
Mundi
1
nasa.gov/images/content/715945main__NOB0093_4x3_516-387.jpg Der wichtige Teil ist, dass es kleiner zu sein scheint als der Bildschirm des Nexus 7.
Mundi
2
Nicht gut. Ich kenne die Höhe nicht - sie ist dynamisch. Dies führt zu unvorhersehbaren Verzerrungen. Die Bearbeitung in meiner Frage deckt dies bereits ab. Entschuldigung - aber danke für deine Bemühungen! Schade bei Google!
Mundi
1
Ich wusste, dass dies eine Option ist, suchte aber nach einer XML- Lösung. Trotzdem sollten Sie dies in Ihrer Antwort bearbeiten. Vielen Dank!
Mundi
8

Dies ist eine kleine Ergänzung zu Mark Martinssons hervorragender Lösung.

Wenn die Breite Ihres Bildes größer als die Höhe ist, lässt Marks Lösung oben und unten auf dem Bildschirm Platz.

Im Folgenden wird dies behoben, indem zuerst Breite und Höhe verglichen werden: Wenn die Bildbreite> = Höhe ist, wird die Höhe an die Bildschirmhöhe angepasst und anschließend die Breite skaliert, um das Seitenverhältnis beizubehalten. Wenn die Bildhöhe> Breite ist, wird die Breite entsprechend der Bildschirmbreite skaliert und anschließend die Höhe skaliert, um das Seitenverhältnis beizubehalten.

Mit anderen Worten, es erfüllt die Definition von scaleType="centerCrop":

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

Skalieren Sie das Bild gleichmäßig (behalten Sie das Seitenverhältnis des Bildes bei), sodass beide Dimensionen (Breite und Höhe) des Bildes gleich oder größer als die entsprechende Dimension der Ansicht sind (minus Auffüllung).

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

Diese Lösung funktioniert automatisch im Hoch- oder Querformat. Sie verweisen in Ihrem Layout genauso wie in Marks Lösung. Z.B:

<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
quelle
3

Ich bin auf dasselbe Problem gestoßen, aber mit einer festen Höhe und einer skalierten Breite, die das ursprüngliche Seitenverhältnis des Bildes beibehält. Ich habe es über ein gewichtetes lineares Layout gelöst. Sie können es hoffentlich an Ihre Bedürfnisse anpassen.

<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
quelle
Diese Lösung hat bei mir hervorragend funktioniert. Dadurch konnte ich meinem ImageView eine bestimmte Höhe geben und das ImageView dynamisch an seine Breite anpassen, um das Seitenverhältnis beizubehalten.
Luke
0

Kotlin-Version von Mark Martinssons Antwort :

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

Eine weitere Ergänzung zu Mark Matinssons Lösung. Ich stellte fest, dass einige meiner Bilder überskaliert waren als andere. Deshalb habe ich die Klasse so geändert, dass das Bild um einen maximalen Faktor skaliert wird. Wenn das Bild zu klein ist, um mit maximaler Breite skaliert zu werden, ohne unscharf zu werden, wird die Skalierung über die maximale Grenze hinaus gestoppt.

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
quelle