Android ImageView skaluje mniejszy obraz do szerokości z elastyczną wysokością bez przycinania i zniekształcania

79

Często pytany, nigdy nie odpowiadał (przynajmniej nie w powtarzalny sposób).

Mam widok obrazu z obrazem, który jest mniejszy niż widok. Chcę przeskalować obraz do szerokości ekranu i dostosować wysokość ImageView, aby odzwierciedlić proporcjonalnie poprawną wysokość obrazu.

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

Skutkuje to wyśrodkowaniem obrazu w jego oryginalnym rozmiarze (mniejszym niż szerokość ekranu) z marginesami z boku. Nie dobrze.

Więc dodałem

android:adjustViewBounds="true" 

Ten sam efekt, niedobry. dodałem

android:scaleType="centerInside"

Ten sam efekt, niedobry. Zmieniłem centerInsidesię fitCenter. Ten sam efekt, niedobry. Zmieniłem centerInsidesię centerCrop.

android:scaleType="centerCrop"

Wreszcie obraz jest skalowany do szerokości ekranu - ale przycięty na górze i na dole! Więc zmieniłem centerCropna fitXY.

android:scaleType="fitXY"

Teraz obraz jest skalowany do szerokości ekranu, ale nie jest skalowany na osi Y, co powoduje zniekształcenie obrazu.

Usunięcie android:adjustViewBounds="true"nie ma żadnego efektu. Dodanie android:layout_gravity, jak zasugerowano w innym miejscu, znowu nie ma żadnego efektu.

Próbowałem innych kombinacji - bezskutecznie. Więc proszę, czy ktoś wie:

Jak skonfigurować XML ImageView, aby wypełniał szerokość ekranu, przeskalował mniejszy obraz, aby wypełnić cały widok, wyświetlając obraz ze współczynnikiem proporcji bez zniekształceń i przycinania?

EDYCJA: Próbowałem też ustawić dowolną wysokość liczbową. Ma to wpływ tylko na centerCropustawienie. Zniekształci obraz w pionie zgodnie z wysokością widoku.

Mundi
źródło
Czy próbowałeś android:scaleType="fitCenter"?
Michael Celey
1
@ BrainSlugs83 Mam i nadal działa dla mnie. Zadano również pytanie, aby określić, czego próbował pytający. Nie ma powodu do złośliwości, zwłaszcza siedem miesięcy po opublikowaniu.
Michael Celey
To nie działa. - Zobacz poniżej; Poza tym wypróbowałem to i mogę sprawdzić, czy nie działa (jeśli wypróbowałeś i widzisz różne wyniki, przedyskutuj poniżej i pokaż nam, co robimy źle - jedyny wniosek, do którego mogę dojść, jest taki, że źle zrozumiałeś pytanie lub nie próbowałem tego).
BrainSlugs83

Odpowiedzi:

110

Rozwiązałem to, tworząc klasę java, którą umieszczasz w pliku układu:

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

Teraz możesz użyć tego, dodając swoją klasę do pliku układu:

<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
źródło
4
+1 Świetne rozwiązanie, działa doskonale również dla Nexusa 7. A najlepsze jest to, że działa również poprawnie w Edytorze Układów w Eclipse.
Ridcully,
Działa na Galaxy S4. Dlaczego nie :) THx
Malachiasz
Dzięki. Świetne rozwiązanie. Wystarczająco łatwe modyfikowanie, aby zrobić to samo w oparciu o heightMeasureSpec.
speedynomads
1
Tak, ale nie ... To rozwiązanie powoduje rozmycie obrazu. Jeśli używam ImageView i fitCenter ze stałą wysokością, obraz nie jest zamazany (ale stała wysokość nie jest dla mnie rozwiązaniem). Jak uniknąć rozmycia?
Geltruda
Szukałem tego rozwiązania od tygodni, super! Chcę jednak przeskalować jeden obraz, ale to działa idealnie
Soko
36

Miałem ten sam problem, co twój. Po 30 minutach prób różnych wariantów rozwiązałem problem w ten sposób. Zapewnia elastyczną wysokość i szerokość dostosowaną do szerokości rodzica

<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
źródło
4
Kluczem jest tutaj użycie AdjustViewBounds
user3690202
@ user3690202 masz rację, to też rozwiązało mój problem. Dzięki
Parth Patel
22

Nie ma realnego rozwiązania w ramach standardu układu XML.

Jedynym niezawodnym sposobem reagowania na dynamiczny rozmiar obrazu jest użycie LayoutParamsw kodzie.

Niezadowalający.

Mundi
źródło
8
  android:scaleType="fitCenter"

Oblicz skalę, która zachowa oryginalny współczynnik proporcji src, ale zapewni również, że src zmieści się całkowicie w dst. Przynajmniej jedna oś (X lub Y) będzie dokładnie pasować. Wynik jest wyśrodkowany wewnątrz dst.

edytować:

Problem polega na tym, że layout_height="wrap_content"nie „pozwala” na rozszerzenie obrazu. Będziesz musiał ustawić dla niego rozmiar, na tę zmianę

  android:layout_height="wrap_content"

do

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

edit2:

działa w porządku:

<ImageView
    android:id="@+id/imageView1"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:scaleType="fitCenter"
    android:src="@drawable/img715945m" />
Budius
źródło
2
W rezultacie powstaje pierwsza wersja obrazu: wyśrodkowana, nie rozwinięta w poziomie, z marginesami po obu stronach. Nie dobrze.
Mundi
2
To nie działa. W rezultacie powstaje obraz o dokładnej wysokości, ale nie rozszerzony w poziomie. Nie dobrze. Zobacz także moją zmianę w pytaniu.
Mundi
1
nasa.gov/images/content/715945main__NOB0093_4x3_516-387.jpg Najważniejsze jest to, że wydaje się być mniejszy niż ekran Nexusa 7.
Mundi
2
Nie dobrze. Nie znam wysokości - jest dynamiczna. Spowoduje to nieprzewidywalne zniekształcenia. Zmiana w moim pytaniu już to obejmuje. Przepraszamy - ale dziękuję za twój wysiłek! Wstyd w Google!
Mundi
1
Wiedziałem, że to opcja, ale szukałem rozwiązania XML . Mimo to powinieneś edytować to w swojej odpowiedzi. Dzięki!
Mundi
8

To mały dodatek do doskonałego rozwiązania Marka Martinssona.

Jeśli szerokość obrazu jest większa niż jego wysokość, rozwiązanie Marka pozostawi miejsce na górze i na dole ekranu.

Poniższe rozwiązanie rozwiązuje ten problem, najpierw porównując szerokość i wysokość: jeśli szerokość obrazu> = wysokość, przeskaluje wysokość, aby dopasować ją do wysokości ekranu, a następnie przeskaluje szerokość, aby zachować proporcje. Podobnie, jeśli wysokość obrazu> szerokość, przeskaluje szerokość w celu dopasowania do szerokości ekranu, a następnie wyskaluje wysokość, aby zachować proporcje.

Innymi słowy, poprawnie spełnia definicję scaleType="centerCrop":

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

Skaluj obraz równomiernie (zachowaj proporcje obrazu), tak aby oba wymiary (szerokość i wysokość) obrazu były równe lub większe niż odpowiadający wymiar widoku (bez wypełnienia).

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

To rozwiązanie automatycznie działa w trybie pionowym lub poziomym. Odwołujesz się do niego w swoim układzie, tak jak robisz to w rozwiązaniu Marka. Na przykład:

<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
źródło
3

Napotkałem ten sam problem, ale ze stałą wysokością i skalowaną szerokością zachowując oryginalne proporcje obrazu. Rozwiązałem to za pomocą ważonego układu liniowego. Miejmy nadzieję, że możesz go zmodyfikować do swoich potrzeb.

<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
źródło
To rozwiązanie zadziałało na mnie świetnie. Pozwoliło mi to nadać mojemu ImageView określoną wysokość i dynamicznie dostosować jego szerokość, aby zachować proporcje.
Łukasz
0

Kotlin wersja Mark Martinsson odpowiedź :

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
źródło
0

Jeszcze jeden dodatek do rozwiązania Marka Matinssona. Zauważyłem, że niektóre z moich obrazów były przeskalowane niż inne. Więc zmodyfikowałem klasę tak, aby obraz był skalowany o maksymalny współczynnik. Jeśli obraz jest zbyt mały, aby można go było przeskalować z maksymalną szerokością bez rozmycia, przestaje skalować poza maksymalny limit.

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
źródło