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.


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>

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

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.


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

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

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>

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

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);
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.