Mam plik, ImageView
który wyświetla png, który ma większy współczynnik proporcji niż urządzenie (w pionie - co oznacza, że jest dłuższy). Chcę to wyświetlić, zachowując proporcje, dopasowując szerokość elementu nadrzędnego i przypinając widok obrazu do góry ekranu.
Problem, który mam z używaniem CENTER_CROP
jako typu skali, polega na tym, że (zrozumiałe) wyśrodkowuje skalowany obraz zamiast wyrównywać górną krawędź do górnej krawędzi widoku obrazu.
Problem FIT_START
polega na tym, że obraz będzie pasował do wysokości ekranu i nie wypełni szerokości.
Rozwiązałem ten problem, używając niestandardowego ImageView oraz zastępując onDraw(Canvas)
i obsługując to ręcznie za pomocą płótna; Problem z tym podejściem polega na tym, że 1) obawiam się, że może być prostsze rozwiązanie, 2) otrzymuję wyjątek VM mem podczas wywoływania super(AttributeSet)
konstruktora podczas próby ustawienia src img na 330kb, gdy sterta ma 3 MB wolnego miejsca (przy wielkości sterty 6 mb) i nie mogę zrozumieć, dlaczego.
Wszelkie pomysły / sugestie / rozwiązania są mile widziane :)
Dzięki
ps Myślałem, że rozwiązaniem może być użycie skali matrycowej i zrobienie tego samodzielnie, ale wydaje mi się, że to taka sama lub więcej pracy niż moje obecne rozwiązanie!
Odpowiedzi:
Ok, mam działające rozwiązanie. Podpowiedź Darko sprawiła, że ponownie spojrzałem na klasę ImageView (dzięki) i zastosowałem transformację za pomocą Matrixa (jak początkowo podejrzewałem, ale nie powiodło się przy mojej pierwszej próbie!). W mojej niestandardowej klasie imageView wywołuję
setScaleType(ScaleType.MATRIX)
posuper()
w konstruktorze i mam następującą metodę.@Override protected boolean setFrame(int l, int t, int r, int b) { Matrix matrix = getImageMatrix(); float scaleFactor = getWidth()/(float)getDrawable().getIntrinsicWidth(); matrix.setScale(scaleFactor, scaleFactor, 0, 0); setImageMatrix(matrix); return super.setFrame(l, t, r, b); }
Umieściłem int w
setFrame()
metodzie, tak jak w ImageView, wywołanieconfigureBounds()
jest w tej metodzie, w której odbywa się całe skalowanie i macierz, więc wydaje mi się to logiczne (powiedz, jeśli się nie zgadzasz)Poniżej znajduje się metoda super.setFrame () z AOSP
@Override protected boolean setFrame(int l, int t, int r, int b) { boolean changed = super.setFrame(l, t, r, b); mHaveFrame = true; configureBounds(); return changed; }
Znajdź tutaj src pełnej klasy
źródło
oto mój kod do wyśrodkowania go na dole. Przy okazji. w kodzie Dori jest mały błąd: ponieważ metoda
super.frame()
jest wywoływana na samym końcu,getWidth()
metoda może zwrócić nieprawidłową wartość. Jeśli chcesz wyśrodkować go u góry, po prostu usuń wiersz postTranslate i gotowe. Fajną rzeczą jest to, że za pomocą tego kodu możesz go przenieść w dowolne miejsce. (po prawej, środek => nie ma problemu;)public class CenterBottomImageView extends ImageView { public CenterBottomImageView(Context context) { super(context); setup(); } public CenterBottomImageView(Context context, AttributeSet attrs) { super(context, attrs); setup(); } public CenterBottomImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setup(); } private void setup() { setScaleType(ScaleType.MATRIX); } @Override protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) { if (getDrawable() == null) { return super.setFrame(frameLeft, frameTop, frameRight, frameBottom); } float frameWidth = frameRight - frameLeft; float frameHeight = frameBottom - frameTop; float originalImageWidth = (float)getDrawable().getIntrinsicWidth(); float originalImageHeight = (float)getDrawable().getIntrinsicHeight(); float usedScaleFactor = 1; if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight)) { // If frame is bigger than image // => Crop it, keep aspect ratio and position it at the bottom and center horizontally float fitHorizontallyScaleFactor = frameWidth/originalImageWidth; float fitVerticallyScaleFactor = frameHeight/originalImageHeight; usedScaleFactor = Math.max(fitHorizontallyScaleFactor, fitVerticallyScaleFactor); } float newImageWidth = originalImageWidth * usedScaleFactor; float newImageHeight = originalImageHeight * usedScaleFactor; Matrix matrix = getImageMatrix(); matrix.setScale(usedScaleFactor, usedScaleFactor, 0, 0); // Replaces the old matrix completly //comment matrix.postTranslate if you want crop from TOP matrix.postTranslate((frameWidth - newImageWidth) /2, frameHeight - newImageHeight); setImageMatrix(matrix); return super.setFrame(frameLeft, frameTop, frameRight, frameBottom); } }
źródło
(r-l)
zamiast tego?if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))
powinna zostać odwrócona? Innymi słowy, czy nie powinieneś testować, czy obraz jest większy niż ramka? Proponuję zastąpić goif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
((View) getParent()).getWidth()
od kiedyImageView
maMATCH_PARENT
Nie musisz pisać niestandardowego widoku obrazu, aby uzyskać dostęp do tej
TOP_CROP
funkcji. Wystarczy zmodyfikowaćmatrix
zImageView
.Ustaw
scaleType
sięmatrix
dlaImageView
:<ImageView android:id="@+id/imageView" android:contentDescription="Image" android:layout_width="match_parent" android:layout_height="match_parent" android:src="@drawable/image" android:scaleType="matrix"/>
Ustaw niestandardową macierz dla
ImageView
:final ImageView imageView = (ImageView) findViewById(R.id.imageView); final Matrix matrix = imageView.getImageMatrix(); final float imageWidth = imageView.getDrawable().getIntrinsicWidth(); final int screenWidth = getResources().getDisplayMetrics().widthPixels; final float scaleRatio = screenWidth / imageWidth; matrix.postScale(scaleRatio, scaleRatio); imageView.setImageMatrix(matrix);
W ten sposób uzyskasz
TOP_CROP
funkcjonalność.źródło
scaleType
wcenterCrop
przeciwnym razie na krawędziach pojawi się puste miejsce.Ten przykład działa z obrazami ładowanymi po utworzeniu obiektu i pewnej optymalizacji. Dodałem kilka komentarzy w kodzie, które wyjaśniają, co się dzieje.
Pamiętaj, aby zadzwonić:
imageView.setScaleType(ImageView.ScaleType.MATRIX);
lub
android:scaleType="matrix"
Źródło Java:
import com.appunite.imageview.OverlayImageView; public class TopAlignedImageView extends ImageView { private Matrix mMatrix; private boolean mHasFrame; @SuppressWarnings("UnusedDeclaration") public TopAlignedImageView(Context context) { this(context, null, 0); } @SuppressWarnings("UnusedDeclaration") public TopAlignedImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } @SuppressWarnings("UnusedDeclaration") public TopAlignedImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); mHasFrame = false; mMatrix = new Matrix(); // we have to use own matrix because: // ImageView.setImageMatrix(Matrix matrix) will not call // configureBounds(); invalidate(); because we will operate on ImageView object } @Override protected boolean setFrame(int l, int t, int r, int b) { boolean changed = super.setFrame(l, t, r, b); if (changed) { mHasFrame = true; // we do not want to call this method if nothing changed setupScaleMatrix(r-l, b-t); } return changed; } private void setupScaleMatrix(int width, int height) { if (!mHasFrame) { // we have to ensure that we already have frame // called and have width and height return; } final Drawable drawable = getDrawable(); if (drawable == null) { // we have to check if drawable is null because // when not initialized at startup drawable we can // rise NullPointerException return; } Matrix matrix = mMatrix; final int intrinsicWidth = drawable.getIntrinsicWidth(); final int intrinsicHeight = drawable.getIntrinsicHeight(); float factorWidth = width/(float) intrinsicWidth; float factorHeight = height/(float) intrinsicHeight; float factor = Math.max(factorHeight, factorWidth); // there magic happen and can be adjusted to current // needs matrix.setTranslate(-intrinsicWidth/2.0f, 0); matrix.postScale(factor, factor, 0, 0); matrix.postTranslate(width/2.0f, 0); setImageMatrix(matrix); } @Override public void setImageDrawable(Drawable drawable) { super.setImageDrawable(drawable); // We have to recalculate image after chaning image setupScaleMatrix(getWidth(), getHeight()); } @Override public void setImageResource(int resId) { super.setImageResource(resId); // We have to recalculate image after chaning image setupScaleMatrix(getWidth(), getHeight()); } @Override public void setImageURI(Uri uri) { super.setImageURI(uri); // We have to recalculate image after chaning image setupScaleMatrix(getWidth(), getHeight()); } // We do not have to overide setImageBitmap because it calls // setImageDrawable method }
źródło
W oparciu o Dori używam rozwiązania, które skaluje obraz na podstawie szerokości lub wysokości obrazu, aby zawsze wypełniał otaczający pojemnik. Pozwala to na skalowanie obrazu w celu wypełnienia całej dostępnej przestrzeni przy użyciu lewego górnego punktu obrazu zamiast środka jako początku (CENTER_CROP):
@Override protected boolean setFrame(int l, int t, int r, int b) { Matrix matrix = getImageMatrix(); float scaleFactor, scaleFactorWidth, scaleFactorHeight; scaleFactorWidth = (float)width/(float)getDrawable().getIntrinsicWidth(); scaleFactorHeight = (float)height/(float)getDrawable().getIntrinsicHeight(); if(scaleFactorHeight > scaleFactorWidth) { scaleFactor = scaleFactorHeight; } else { scaleFactor = scaleFactorWidth; } matrix.setScale(scaleFactor, scaleFactor, 0, 0); setImageMatrix(matrix); return super.setFrame(l, t, r, b); }
Mam nadzieję, że to pomoże - działa jak gratka w moim projekcie.
źródło
Żadne z tych rozwiązań nie zadziałało dla mnie, ponieważ chciałem klasy, która wspierałaby dowolną uprawę z kierunku poziomego lub pionowego i chciałem, aby umożliwiała mi dynamiczną zmianę upraw. Potrzebowałem również kompatybilności z Picassem , a Picasso leniwie ustawia rysunki obrazów.
Moja implementacja jest dostosowywana bezpośrednio z ImageView.java w AOSP. Aby z niego skorzystać, zadeklaruj tak w XML:
<com.yourapp.PercentageCropImageView android:id="@+id/view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix"/>
Ze źródła, jeśli chcesz uzyskać najlepsze plony, zadzwoń:
imageView.setCropYCenterOffsetPct(0f);
Jeśli chcesz mieć najniższe plony, zadzwoń:
imageView.setCropYCenterOffsetPct(1.0f);
Jeśli chcesz mieć uprawę 1/3 drogi w dół, zadzwoń:
imageView.setCropYCenterOffsetPct(0.33f);
Co więcej, jeśli zdecydujesz się użyć innej metody przycinania, takiej jak fit_center, możesz to zrobić i żadna z tej niestandardowej logiki nie zostanie wyzwolona. (Inne implementacje TYLKO pozwalają ci używać ich metod przycinania).
Na koniec dodałem metodę redraw (), więc jeśli zdecydujesz się zmienić metodę przycinania / typ skali dynamicznie w kodzie, możesz zmusić widok do przerysowania. Na przykład:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); fullsizeImageView.redraw();
Aby wrócić do niestandardowego trzeciego przycięcia w górnej środkowej części, zadzwoń:
fullsizeImageView.setScaleType(ScaleType.MATRIX); fullsizeImageView.redraw();
Oto klasa:
/* * Adapted from ImageView code at: * http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.4_r1/android/widget/ImageView.java */ import android.content.Context; import android.graphics.Matrix; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; public class PercentageCropImageView extends ImageView{ private Float mCropYCenterOffsetPct; private Float mCropXCenterOffsetPct; public PercentageCropImageView(Context context) { super(context); } public PercentageCropImageView(Context context, AttributeSet attrs) { super(context, attrs); } public PercentageCropImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public float getCropYCenterOffsetPct() { return mCropYCenterOffsetPct; } public void setCropYCenterOffsetPct(float cropYCenterOffsetPct) { if (cropYCenterOffsetPct > 1.0) { throw new IllegalArgumentException("Value too large: Must be <= 1.0"); } this.mCropYCenterOffsetPct = cropYCenterOffsetPct; } public float getCropXCenterOffsetPct() { return mCropXCenterOffsetPct; } public void setCropXCenterOffsetPct(float cropXCenterOffsetPct) { if (cropXCenterOffsetPct > 1.0) { throw new IllegalArgumentException("Value too large: Must be <= 1.0"); } this.mCropXCenterOffsetPct = cropXCenterOffsetPct; } private void myConfigureBounds() { if (this.getScaleType() == ScaleType.MATRIX) { /* * Taken from Android's ImageView.java implementation: * * Excerpt from their source: } else if (ScaleType.CENTER_CROP == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx = 0, dy = 0; if (dwidth * vheight > vwidth * dheight) { scale = (float) vheight / (float) dheight; dx = (vwidth - dwidth * scale) * 0.5f; } else { scale = (float) vwidth / (float) dwidth; dy = (vheight - dheight * scale) * 0.5f; } mDrawMatrix.setScale(scale, scale); mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); } */ Drawable d = this.getDrawable(); if (d != null) { int dwidth = d.getIntrinsicWidth(); int dheight = d.getIntrinsicHeight(); Matrix m = new Matrix(); int vwidth = getWidth() - this.getPaddingLeft() - this.getPaddingRight(); int vheight = getHeight() - this.getPaddingTop() - this.getPaddingBottom(); float scale; float dx = 0, dy = 0; if (dwidth * vheight > vwidth * dheight) { float cropXCenterOffsetPct = mCropXCenterOffsetPct != null ? mCropXCenterOffsetPct.floatValue() : 0.5f; scale = (float) vheight / (float) dheight; dx = (vwidth - dwidth * scale) * cropXCenterOffsetPct; } else { float cropYCenterOffsetPct = mCropYCenterOffsetPct != null ? mCropYCenterOffsetPct.floatValue() : 0f; scale = (float) vwidth / (float) dwidth; dy = (vheight - dheight * scale) * cropYCenterOffsetPct; } m.setScale(scale, scale); m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); this.setImageMatrix(m); } } } // These 3 methods call configureBounds in ImageView.java class, which // adjusts the matrix in a call to center_crop (android's built-in // scaling and centering crop method). We also want to trigger // in the same place, but using our own matrix, which is then set // directly at line 588 of ImageView.java and then copied over // as the draw matrix at line 942 of ImageVeiw.java @Override protected boolean setFrame(int l, int t, int r, int b) { boolean changed = super.setFrame(l, t, r, b); this.myConfigureBounds(); return changed; } @Override public void setImageDrawable(Drawable d) { super.setImageDrawable(d); this.myConfigureBounds(); } @Override public void setImageResource(int resId) { super.setImageResource(resId); this.myConfigureBounds(); } public void redraw() { Drawable d = this.getDrawable(); if (d != null) { // Force toggle to recalculate our bounds this.setImageDrawable(null); this.setImageDrawable(d); } } }
źródło
Może przejdź do kodu źródłowego widoku obrazu na Androidzie i zobacz, jak rysuje środkowy kadr itp., A może skopiuj część tego kodu do swoich metod. Naprawdę nie znam lepszego rozwiązania niż zrobienie tego. Mam doświadczenie w ręcznym zmienianiu rozmiaru i przycinaniu mapy bitowej (wyszukiwanie transformacji bitmapy), co zmniejsza jej rzeczywisty rozmiar, ale nadal powoduje pewne obciążenie w procesie.
źródło
public class ImageViewTopCrop extends ImageView { public ImageViewTopCrop(Context context) { super(context); setScaleType(ScaleType.MATRIX); } public ImageViewTopCrop(Context context, AttributeSet attrs) { super(context, attrs); setScaleType(ScaleType.MATRIX); } public ImageViewTopCrop(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); setScaleType(ScaleType.MATRIX); } @Override protected boolean setFrame(int l, int t, int r, int b) { computMatrix(); return super.setFrame(l, t, r, b); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); computMatrix(); } private void computMatrix() { Matrix matrix = getImageMatrix(); float scaleFactor = getWidth() / (float) getDrawable().getIntrinsicWidth(); matrix.setScale(scaleFactor, scaleFactor, 0, 0); setImageMatrix(matrix); }
}
źródło
onLayout
dużo mnie oszczędza! Dzięki! Napotkałem problem polegający na tym, że oblicza matrycę, ale nie wyświetla obrazu od razu, a dodanieonLayout
kodu rozwiązuje mój problem.Jeśli korzystasz z Fresco (SimpleDraweeView), możesz to łatwo zrobić za pomocą:
PointF focusPoint = new PointF(0.5f, 0f); imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
Ten byłby dla najlepszych roślin.
Więcej informacji na stronie odnośnika
źródło
Istnieją 2 problemy z rozwiązaniami tutaj:
Ta niewielka modyfikacja rozwiązuje problem (umieść kod w onDraw i sprawdź współczynniki skali szerokości i wysokości):
@Override protected void onDraw(Canvas canvas) { Matrix matrix = getImageMatrix(); float scaleFactorWidth = getWidth() / (float) getDrawable().getIntrinsicWidth(); float scaleFactorHeight = getHeight() / (float) getDrawable().getIntrinsicHeight(); float scaleFactor = (scaleFactorWidth > scaleFactorHeight) ? scaleFactorWidth : scaleFactorHeight; matrix.setScale(scaleFactor, scaleFactor, 0, 0); setImageMatrix(matrix); super.onDraw(canvas); }
źródło
Najprostsze rozwiązanie: przytnij obraz
@Override public void draw(Canvas canvas) { if(getWidth() > 0){ int clipHeight = 250; canvas.clipRect(0,clipHeight,getWidth(),getHeight()); } super.draw(canvas); }
źródło