Android: Jak rozciągnąć obraz do szerokości ekranu, zachowując proporcje?

118

Chcę pobrać obraz (o nieznanym rozmiarze, ale zawsze mniej więcej kwadratowy) i wyświetlić go tak, aby wypełniał ekran w poziomie i rozciągał się w pionie, aby zachować proporcje obrazu, na dowolnym rozmiarze ekranu. Oto mój (niedziałający) kod. Rozciąga obraz w poziomie, ale nie w pionie, więc jest ściśnięty ...

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
źródło
czy wszystkie urządzenia z systemem Android mają dokładnie ten sam stosunek szerokości do wysokości? Jeśli nie, po prostu niemożliwe jest przeskalowanie obrazu tak, aby pasował do całej szerokości / wysokości, zachowując oryginalne proporcje ...
NoozNooz42
4
Nie, nie chcę, aby obraz wypełniał ekran, tylko po to, aby przeskalować do szerokości ekranu, nie obchodzi mnie, ile ekranu zajmuje obraz w pionie, o ile obraz jest w odpowiednich proporcjach.
fredley,
1
Podobne pytanie, dobra odpowiedź: stackoverflow.com/questions/4677269/ ...
Pēteris Caune
1
czy „adjustViewBounds” nie działał?
Darpan

Odpowiedzi:

132

Osiągnąłem to za pomocą niestandardowego widoku. Ustaw layout_width = "fill_parent" i layout_height = "wrap_content" i wskaż odpowiedni element do rysowania:

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
źródło
12
Dziękuję Ci!! To powinna być prawidłowa odpowiedź! :) Dostosowałem to dla trochę większej elastyczności w tym poście: stackoverflow.com/questions/4677269/ ... Tam używam ImageView, więc można ustawić drawable w XML lub użyć go jak normalnego ImageView w kodzie, aby ustawić obraz. Działa świetnie!
Patrick Boos,
1
Niesamowity geniusz! to działało jak mistrz! Dzięki temu rozwiązałem mój problem z banerem graficznym u góry, który nie był odpowiedni na wszystkich rozmiarach ekranu! Dziękuję bardzo!
JPM
1
Uważaj na zerowe logo (jeśli pobierasz zawartość z Internetu). W przeciwnym razie działa świetnie, postępuj zgodnie z postem Patricka, aby uzyskać fragment kodu XML.
Artem Russakovskii
44
Nie mogę uwierzyć, jak trudno jest robić rzeczy na Androidzie, które są banalnie łatwe w iOS i Windows Phone.
mxcl
1
Wprowadziłem pewne zmiany, ale zamiast opublikować je w innym miejscu, czy można byłoby zamienić tę odpowiedź na wiki społeczności? Moje zmiany to wszystkie rzeczy, które obsługują wspomniane tutaj specjalne przypadki, a także możliwość wyboru strony, której rozmiar zostanie zmieniony w ramach definicji układu.
Steve Pomeroy,
24

W końcu wymiary wygenerowałem ręcznie, co działa świetnie:

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
źródło
Szukałem tego rodzaju rozwiązania, aby naprawić mój problem, z pomocą tego obliczenia udało mi się pomyślnie załadować obraz bez zmniejszania proporcji.
Steve
21

Właśnie przeczytałem kod źródłowy ImageViewi jest to w zasadzie niemożliwe bez wykorzystania rozwiązań podklas w tym wątku. W ImageView.onMeasuredochodzimy do tych linii:

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

Gdzie hi wsą wymiary obrazu i p*jest wypełnieniem.

I wtedy:

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

Więc jeśli masz, layout_height="wrap_content"to ustawi widthSize = w + pleft + pright, lub innymi słowy, maksymalna szerokość jest równa szerokości obrazu.

Oznacza to, że jeśli nie ustawisz dokładnego rozmiaru, obrazy NIGDY nie będą powiększane . Uważam, że to błąd, ale powodzenia, aby Google to zauważyło lub naprawiło. Edycja: Jedząc własne słowa, przesłałem raport o błędzie i mówią, że został on naprawiony w przyszłym wydaniu!

Inne rozwiązanie

Oto kolejne obejście z podklasą, ale powinieneś (teoretycznie nie testowałem go zbyt wiele!), Aby móc go używać w dowolnym miejscu ImageView. Aby go użyć, ustaw layout_width="match_parent"i layout_height="wrap_content". Jest też dużo bardziej ogólne niż przyjęte rozwiązanie. Np. Możesz dopasować do wysokości i do szerokości.

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
źródło
Złożyłem raport o błędzie . Uważaj, jak jest ignorowane!
Timmmm
3
Najwyraźniej muszę zjeść swoje słowa - mówią, że zostało to naprawione w przyszłym wydaniu!
Timmmm
Dziękuję bardzo Tim. To powinna być ostatnia prawidłowa odpowiedź. Szukałem dużo czasu i nic nie jest lepsze niż Twoje rozwiązanie.!
tainy
co, jeśli muszę ustawić maxHeight podczas korzystania z powyższego StretchyImageView. Nie znalazłem na to żadnego rozwiązania.
tainy
17

Ustawienie adjustViewBounds na true i użycie grupy widoków LinearLayout zadziałało bardzo dobrze. Nie ma potrzeby tworzenia podklas ani pytania o metryki urządzenia:

//NOTE: "this" is a subclass of LinearLayout
ImageView splashImageView = new ImageView(context);
splashImageView.setImageResource(R.drawable.splash);
splashImageView.setAdjustViewBounds(true);
addView(splashImageView);
Matt Stoker
źródło
1
... lub używając właściwości XML ImageView - android: adjustViewBounds = "true"
Anatoliy Shuba
Idealny! To było takie proste i idealnie rozciągało obraz. Czy ta odpowiedź nie jest poprawna? Odpowiedź z CustomView nie zadziałała dla mnie (jakiś dziwny błąd xml)
user3800924
13

Walczę z tym problemem w takiej czy innej formie dla AGES, dziękuję, dziękuję, dziękuję .... :)

Chciałem tylko wskazać, że można uzyskać uogólnione rozwiązanie z tego, co zrobił Bob Lee, po prostu rozszerzając Widok i zastępując onMeasure. W ten sposób możesz użyć tego z dowolnym rysunkiem, który chcesz i nie zepsuje się, jeśli nie ma obrazu:

    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
źródło
2
Spośród wielu, wielu rozwiązań tego problemu, jest to pierwsze, które jest tylko rozwiązaniem typu drop-in, gdzie inne zawsze mają wadę (na przykład brak możliwości zmiany obrazu w czasie wykonywania bez ponownego zapisu kodu). Dziękuję Ci!
MacD
To czysta niesamowita rzecz - walczę z tym już od jakiegoś czasu, a wszystkie oczywiste rozwiązania wykorzystujące atrybuty xml nigdy nie działały. Dzięki temu mogłem po prostu zastąpić mój ImageView rozszerzonym widokiem i wszystko działało zgodnie z oczekiwaniami!
slott
To absolutnie najlepsze rozwiązanie tego problemu.
erlando
To świetna odpowiedź. Możesz używać tej klasy w pliku xml bez obaw: <com.yourpackagename.CardImageView android: layout_width = "fill_parent" android: layout_height = "wrap_content" android: background = "@ drawable / your_photo" />. Wspaniały!
arniotaki
11

W niektórych przypadkach ta magiczna formuła pięknie rozwiązuje problem.

Dla każdego, kto boryka się z tym problemem pochodzącym z innej platformy, „rozmiar i kształt do dopasowania” opcja jest obsługiwana pięknie w Androidzie, ale trudno ją znaleźć.

Zwykle potrzebujesz tej kombinacji:

  • element nadrzędny dopasowania szerokości,
  • zawartość zawijania wysokości,
  • AdjustViewBounds włączone (sic)
  • skala fitCenter
  • cropToPadding WYŁĄCZONY (sic)

Wtedy jest to automatyczne i niesamowite.

Jeśli jesteś programistą iOS, to niesamowite, jak prosto w Androidzie możesz wykonywać „całkowicie dynamiczne wysokości komórek” w widoku tabeli… eee, mam na myśli ListView. Cieszyć się.

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

wprowadź opis obrazu tutaj

Fattie
źródło
6

Udało mi się to osiągnąć używając tylko tego kodu XML. Może się zdarzyć, że zaćmienie nie wyrenderuje wysokości, aby pokazać, że rozszerza się w celu dopasowania; Jednak gdy faktycznie uruchomisz to na urządzeniu, renderuje się prawidłowo i zapewnia pożądany rezultat. (dobrze przynajmniej dla mnie)

<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
źródło
Przepraszamy, to nie działa. Skaluje szerokość, ale centerCrop nie zachowuje proporcji.
ricosrealm
1
Po wielu godzinach zmagań z niestandardowymi klasami rozszerzającymi ImageView (jak napisano w wielu odpowiedziach i artykułach) i mając wyjątek typu „Nie można znaleźć następujących klas”, usunąłem ten kod. Nagle zauważyłem, że problem w moim ImageView był w atrybucie tła! Zmieniłem to na srci ImageView stał się proporcjonalny.
CoolMind
5

Zrobiłem to z tymi wartościami w LinearLayout:

Scale type: fitStart
Layout gravity: fill_horizontal
Layout height: wrap_content
Layout weight: 1
Layout width: fill_parent
holduel
źródło
Czy mógłbyś podać prawdziwy przykład? Gdzie umieścić te wartości w moim pliku xml?
John Smith Opcjonalnie
5

Wszyscy robią to programowo, więc pomyślałem, że ta odpowiedź będzie tutaj idealnie pasować. Ten kod działał dla my w xml. NIE myślę jeszcze o współczynniku, ale nadal chciałem umieścić tę odpowiedź, jeśli to komuś pomogłoby.

android:adjustViewBounds="true"

Twoje zdrowie..

Sindri Þór
źródło
3

Bardzo prostym rozwiązaniem jest po prostu skorzystanie z funkcji udostępnianych przez RelativeLayout.

Oto plik XML, który umożliwia to w przypadku standardowego Androida 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>

Sztuczka polega na tym, że ustawiłeś, ImageViewaby wypełniał ekran, ale musi znajdować się nad innymi układami. W ten sposób osiągniesz wszystko, czego potrzebujesz.

Warpzit
źródło
2

Jego prosta kwestia ustawienia adjustViewBounds="true"i scaleType="fitCenter"pliku XML dla ImageView!

<ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:src="@drawable/image"

        android:adjustViewBounds="true"
        android:scaleType="fitCenter"
        />

Uwaga: layout_widthjest ustawiony namatch_parent

Kaushik NP
źródło
1

Ustawiasz ScaleType na ScaleType.FIT_XY. Według javadocs , spowoduje to rozciągnięcie obrazu tak, aby pasował do całego obszaru, zmieniając w razie potrzeby proporcje. To wyjaśniałoby zachowanie, które widzisz.

Aby uzyskać pożądane zachowanie ... FIT_CENTER, FIT_START lub FIT_END są zbliżone, ale jeśli obraz jest węższy niż wysoki, nie zacznie wypełniać szerokości. Możesz jednak przyjrzeć się, jak są one wdrażane, i prawdopodobnie powinieneś być w stanie dowiedzieć się, jak dostosować je do swoich celów.

Cheryl Simon
źródło
2
Próbowałem FIT_CENTER, ale nie inne. Niestety one też nie działają, oba z setAdjustViewBounds ustawionym na true i false. Czy jest tak, aby uzyskać szerokość w pikselach ekranu urządzenia, a także szerokość / wysokość pobranego obrazu i ręcznie ustawić rozmiar?
fredley
1

ScaleType.CENTER_CROP zrobi, co chcesz: rozciągnie do pełnej szerokości i odpowiednio skaluje wysokość. jeśli wyskalowana wysokość przekracza granice ekranu, obraz zostanie przycięty.

P.Melch
źródło
1

Spójrz, istnieje o wiele łatwiejsze rozwiązanie twojego problemu:

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);
}
user3673209
źródło
1

Możesz użyć mojego StretchableImageView z zachowaniem proporcji (według szerokości lub wysokości) w zależności od szerokości i wysokości rysowanego elementu:

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

Dla mnie android: scaleType = "centerCrop" nie rozwiązał mojego problemu. To faktycznie znacznie rozszerzyło obraz. Próbowałem więc z androidem: scaleType = "fitXY" i działało doskonale.

Ricardo
źródło
0

To działa dobrze, jak na moje wymagania

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

Anand Savjani
źródło