Skaluj obraz, aby wypełnić szerokość ImageView i zachować proporcje

198

Mam GridView. Dane GridViewsą żądaniem z serwera.

Oto układ przedmiotu w GridView:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/analysis_micon_bg"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:paddingBottom="@dimen/half_activity_vertical_margin"
    android:paddingLeft="@dimen/half_activity_horizontal_margin"
    android:paddingRight="@dimen/half_activity_horizontal_margin"
    android:paddingTop="@dimen/half_activity_vertical_margin" >

    <ImageView
        android:id="@+id/ranking_prod_pic"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:contentDescription="@string/app_name"
        android:scaleType="centerCrop" />

    <TextView
        android:id="@+id/ranking_rank_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/ranking_prod_num"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/ranking_prod_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>

Proszę o dane z serwera, pobieram adres URL obrazu i ładuję obraz do Bitmap

public static Bitmap loadBitmapFromInputStream(InputStream is) {
    return BitmapFactory.decodeStream(is);
}

public static Bitmap loadBitmapFromHttpUrl(String url) {
    try {
        return loadBitmapFromInputStream((InputStream) (new URL(url).getContent()));
    } catch (Exception e) {
        Log.e(TAG, e.getMessage());
        return null;
    }
}

i jest kod getView(int position, View convertView, ViewGroup parent)metody w adapterze

Bitmap bitmap = BitmapUtil.loadBitmapFromHttpUrl(product.getHttpUrl());
prodImg.setImageBitmap(bitmap);

Rozmiar obrazu to 210*210. Uruchamiam aplikację na moim Nexusie 4. Obraz wypełnia ImageViewszerokość, ale ImageViewwysokość nie jest skalowana. ImageViewnie pokazuje całego obrazu.

Jak rozwiązać ten problem?

Joe
źródło

Odpowiedzi:

545

Bez użycia niestandardowych klas lub bibliotek:

<ImageView
    android:id="@id/img"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"
    android:scaleType="fitCenter" />

scaleType="fitCenter" (domyślnie gdy pominięty)

  • sprawi, że będzie tak szeroki, jak pozwala na to rodzic, i odpowiednio zwiększy / zmniejszy skalę, zachowując proporcje.

scaleType="centerInside"

  • jeśli rzeczywista szerokość srcjest mniejsza niż szerokość rodzica
    , wyśrodkuje obraz w poziomie
  • jeśli rzeczywista szerokość srcjest większa niż szerokość rodzica,
    spowoduje, że będzie ona tak szeroka, jak pozwala rodzic, i zachowa proporcje w dół.

Nie ma znaczenia, czy używasz metod android:srcczy ImageView.setImage*metod, a kluczem jest prawdopodobnie adjustViewBounds.

TWiStErRob
źródło
5
android: layout_height = "wrap_content" android: adjustViewBounds = "true" android: scaleType = "fitCenter" załatwia sprawę
James Tan
@JamesTan fill_parentdla szerokości jest tylko dobrą praktyką, działa również ustalony rozmiar.
TWiStErRob
@TWiStErRob Zdaję sobie sprawę, że potrzebuje Androida: adjustViewBounds = „true”, w przeciwnym razie nie będzie pasować do wysokości. i tak, szerokość pasująca do rodzica jest pełną wersją
James Tan
9
Działa jak urok na API 19+, ale nie na API 16 (testowane). Niestandardowy widok Alexa Semeniuka działa również na API 16.
Ashkan Sarlak
1
@ F.Mysir Whops 😅, tak, nie ma znaczenia, który z nich dotyczy problemu, ale jeśli chodzi o rozpowszechnianie dobrych praktyk, całkowicie się zgadzam.
TWiStErRob
42

Lubię odpowiedź arnefma, ale popełnił mały błąd (patrz komentarze), który postaram się poprawić:

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

/**
 * ImageView that keeps aspect ratio when scaled
 */
public class ScaleImageView extends ImageView {

  public ScaleImageView(Context context) {
    super(context);
  }

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

  public ScaleImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    try {
      Drawable drawable = getDrawable();
      if (drawable == null) {
        setMeasuredDimension(0, 0);
      } else {
        int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
        int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
        if (measuredHeight == 0 && measuredWidth == 0) { //Height and width set to wrap_content
          setMeasuredDimension(measuredWidth, measuredHeight);
        } else if (measuredHeight == 0) { //Height set to wrap_content
          int width = measuredWidth;
          int height = width *  drawable.getIntrinsicHeight() / drawable.getIntrinsicWidth();
          setMeasuredDimension(width, height);
        } else if (measuredWidth == 0){ //Width set to wrap_content
          int height = measuredHeight;
          int width = height * drawable.getIntrinsicWidth() / drawable.getIntrinsicHeight();
          setMeasuredDimension(width, height);
        } else { //Width and height are explicitly set (either to match_parent or to exact value)
          setMeasuredDimension(measuredWidth, measuredHeight);
        }
      }
    } catch (Exception e) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
  }

}

W ten sposób ImageViewzostanie odpowiednio skalowane i nie będzie mieć problemów z wymiarami, jeśli (na przykład) zostanie włożonyScrollView

Alex Semeniuk
źródło
dzięki, znalazłem rozwiązanie, tuż poniżej odpowiedzi arnefm, pierwszego komentarza, który dodałem. to therejest link
Joe
36

Miałem kiedyś podobny problem. Rozwiązałem to, tworząc niestandardowy ImageView.

public class CustomImageView extends ImageView

Następnie zastąp metodę onMeasure widoku obrazu. Zrobiłem coś takiego, jak sądzę:

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    try {
        Drawable drawable = getDrawable();

        if (drawable == null) {
            setMeasuredDimension(0, 0);
        } else {
            float imageSideRatio = (float)drawable.getIntrinsicWidth() / (float)drawable.getIntrinsicHeight();
            float viewSideRatio = (float)MeasureSpec.getSize(widthMeasureSpec) / (float)MeasureSpec.getSize(heightMeasureSpec);
            if (imageSideRatio >= viewSideRatio) {
                // Image is wider than the display (ratio)
                int width = MeasureSpec.getSize(widthMeasureSpec);
                int height = (int)(width / imageSideRatio);
                setMeasuredDimension(width, height);
            } else {
                // Image is taller than the display (ratio)
                int height = MeasureSpec.getSize(heightMeasureSpec);
                int width = (int)(height * imageSideRatio);
                setMeasuredDimension(width, height);
            }
        }
    } catch (Exception e) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

Spowoduje to rozciągnięcie obrazu w celu dopasowania do ekranu przy zachowaniu współczynnika proporcji.

arnefm
źródło
1
patrz tam . pierwsza odpowiedź
Joe,
Cóż, ta odpowiedź nie jest dokładna. Rozważ sytuację, w której ustawiłeś android:layout_height="wrap_content". W tym przypadku MeasureSpec.getSize(heightMeasureSpec)będzie, 0a twój viewSideRatiospowoduje Infinity(nie trzeba się zastanawiać, Java float dopuszcza takie wartości.) W tym przypadku zarówno twój, jak heighti widthbędzie 0.
Alex Semeniuk
1
Aby wypełnienie działało, musiałem zamienić (imageSideRatio> = viewSideRatio) na (imageSideRatio <viewSideRatio) .. w przeciwnym razie dobra odpowiedź
Marek Halmo
23

Zastosowanie android:scaleType="centerCrop".

Mark Buikema
źródło
Nie do końca o to pytanie, ale dokładnie to, czego potrzebowałem!
nickjm
Czy potrafisz opracować @Jameson? Masz na myśli wersje Androida?
Mick
Jeśli użyję CenterCrop, obraz zostanie nieco przycięty na górze i na dole.
Sreekanth Karumanaghat
9

Zrobiłem coś podobnego do powyższego, a następnie uderzyłem głową o ścianę przez kilka godzin, ponieważ to nie działało w środku RelativeLayout. Skończyło się na następującym kodzie:

package com.example;

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

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

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

        if (d != null) {
            int width;
            int height;
            if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
                height = MeasureSpec.getSize(heightMeasureSpec);
                width = (int) Math.ceil(height * (float) d.getIntrinsicWidth() / d.getIntrinsicHeight());
            } else {
                width = MeasureSpec.getSize(widthMeasureSpec);
                height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            }
            setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

Aby zapobiec RelativeLayoutignorowaniu mierzonego wymiaru, zrobiłem to:

    <FrameLayout
        android:id="@+id/image_frame"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/something">

        <com.example.ScaledImageView
            android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="150dp"/>
    </FrameLayout>
Dave Cole
źródło
5

Użyj tych właściwości w ImageView, aby zachować proporcje:

android:adjustViewBounds="true"
android:scaleType="fitXY"
Atif Mahmood
źródło
9
fitXY NIE zachowuje proporcji. Rozciąga obraz dokładnie w celu dopasowania do szerokości i wysokości układu.
Opuszczony
5

Nie będzie to miało zastosowania, jeśli ustawisz obraz jako tło w ImageView, musisz ustawić w src (android: src).

Dzięki.

Guna Sekhar
źródło
5

Aby utworzyć obraz o szerokości równej szerokości ekranu i wysokości proporcjonalnie ustawionej zgodnie z proporcjami obrazu, wykonaj następujące czynności.

Glide.with(context).load(url).asBitmap().into(new SimpleTarget<Bitmap>() {
                    @Override
                    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {

                        // creating the image that maintain aspect ratio with width of image is set to screenwidth.
                        int width = imageView.getMeasuredWidth();
                        int diw = resource.getWidth();
                        if (diw > 0) {
                            int height = 0;
                            height = width * resource.getHeight() / diw;
                            resource = Bitmap.createScaledBitmap(resource, width, height, false);
                        }
                                              imageView.setImageBitmap(resource);
                    }
                });

Mam nadzieję że to pomoże.

Fathima km
źródło
to najlepsza odpowiedź, jaką znalazłem, szukam 2 dni, dzięki fathima
Ramanathan
4

Spróbuj tego: to rozwiązało problem

   android:adjustViewBounds="true"
    android:scaleType="fitXY"
Inżynieria umysłu
źródło
4

Nie potrzebujesz kodu Java. Ty po prostu musisz :

<ImageView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:adjustViewBounds="true"
    android:scaleType="centerCrop" />

Klucz znajduje się w rodzicu dopasowania dla szerokości i wysokości

Romu Dizzy
źródło
CenterCrop faktycznie wycina część obrazu.
Sreekanth Karumanaghat
2

wypróbuj tę prostą linię ... dodaj tę linię do kodu xml w tagu widoku obrazu bez dodawania jakiejkolwiek zależności android: scaleType = "fitXY"

ziddi609
źródło
fitXY nie utrzymuje proporcji, więc obraz prawdopodobnie zostanie rozciągnięty
ProjectDelta
2

użyj Androida: ScaleType = "fitXY" im ImageView xml

Naveed Naeem
źródło
fitXY nie utrzymuje proporcji, więc obraz prawdopodobnie zostanie rozciągnięty
ProjectDelta
1

Możesz spróbować zrobić to, co robisz, ręcznie ładując obrazy, ale bardzo zdecydowanie zaleciłbym spojrzenie na Universal Image Loader .

Niedawno zintegrowałem go z moim projektem i muszę powiedzieć, że jest fantastyczny. Czy martwi Cię tworzenie asynchronicznych, zmienianie rozmiaru, buforowanie obrazów dla Ciebie. Naprawdę łatwo go zintegrować i skonfigurować. W ciągu 5 minut możesz prawdopodobnie zrobić to, co chcesz.

Przykładowy kod:

//ImageLoader config
DisplayImageOptions displayimageOptions = new DisplayImageOptions.Builder().showStubImage(R.drawable.downloadplaceholder).cacheInMemory().cacheOnDisc().showImageOnFail(R.drawable.loading).build();

    ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()).
            defaultDisplayImageOptions(displayimageOptions).memoryCache(new WeakMemoryCache()).discCache(new UnlimitedDiscCache(cacheDir)).build();

    if (ImageLoader.getInstance().isInited()) {
        ImageLoader.getInstance().destroy();
    }
    ImageLoader.getInstance().init(config);

    imageLoadingListener = new ImageLoadingListener() {
        @Override
        public void onLoadingStarted(String s, View view) {

        }

        @Override
        public void onLoadingFailed(String s, View view, FailReason failReason) {
            ImageView imageView = (ImageView) view;
            imageView.setImageResource(R.drawable.android);
            Log.i("Failed to Load " + s, failReason.toString());
        }

        @Override
        public void onLoadingComplete(String s, View view, Bitmap bitmap) {

        }

        @Override
        public void onLoadingCancelled(String s, View view) {

        }
    };

//Imageloader usage
ImageView imageView = new ImageView(getApplicationContext());
    if (orientation == 1) {
        imageView.setLayoutParams(new LinearLayout.LayoutParams(width / 6, width / 6));
    } else {
        imageView.setLayoutParams(new LinearLayout.LayoutParams(height / 6, height / 6));
    }
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageLoader.displayImage(SERVER_HOSTNAME + "demos" + demo.getPathRoot() + demo.getRootName() + ".png", imageView, imageLoadingListener);

To może leniwie ładować obrazy, dopasować je poprawnie do rozmiaru imageView pokazując obraz zastępczy podczas ładowania i pokazując domyślną ikonę, jeśli ładowanie się nie powiedzie i buforowanie zasobów.

- Powinienem również dodać, że ta bieżąca konfiguracja zachowuje proporcje obrazu, dlatego ma zastosowanie do twojego pierwotnego pytania

rcbevans
źródło
0

Wystarczy użyć UniversalImageLoader i ustawić

DisplayImageOptions.Builder()
    .imageScaleType(ImageScaleType.EXACTLY_STRETCHED)
    .build();

i brak ustawień skali w ImageView

Artem
źródło
0

Miałem podobny problem, znalazłem przyczynę tego, ponieważ musisz obliczyć dp. Android studio obliczania ImageViewpodczas ładowania go z drawable, ale jeśli używasz innej metody, jak załadunek z bitmapydp nie jest automatycznie rozliczane,

Oto mój xml

<ImageView
  android:id="@+id/imageViewer"
  android:layout_width="match_parent"
  android:layout_height="match_parent"//dp is not automaticly updated, when loading from a other source
  android:scaleType="fitCenter"
  tools:srcCompat="@drawable/a8" />

Używam Kotlina i ładuję pliki do pobrania z pliku zasobów, oto jak to obliczam

val d = Drawable.createFromStream(assets.open("imageData/${imageName}.png"), null)
bitHeight = d.minimumHeight//get the image height
imageViewer.layoutParams.height = (bitHeight * resources.displayMetrics.density).toInt()//set the height
imageViewer.setImageDrawable(d)//set the image from the drawable
imageViewer.requestLayout()//here I apply it to the layout
DarkPh03n1X
źródło
-1

Użyj Picasso, który jest łatwy w użyciu ..

W twoim adapterze ..

@Override
public void getView(int position, View convertView, ViewGroup parent) {

 ImageView view = (ImageView) convertView.findViewById(R.id.ranking_prod_pic);

 Picasso.with(context).load(url).into(view); //url is image url

 //you can resize image if you want

 /* Picasso.with(context) .load(url) .resize(50, 50) .centerCrop() .into(view) */

}

http://square.github.io/picasso/ wprowadź opis zdjęcia tutaj

chiragkyada
źródło