У меня ImageView
есть изображение png, которое имеет большее соотношение сторон, чем у устройства (вертикально говоря, то есть длиннее). Я хочу отобразить это, сохраняя соотношение сторон, соответствие ширине родительского элемента и закрепление изображения в верхней части экрана.
Проблема, с которой я сталкиваюсь с использованием CENTER_CROP
в качестве типа шкалы, заключается в том, что он (понятно) центрирует масштабированное изображение вместо выравнивания верхнего края с верхним краем представления изображения.
Проблема FIT_START
в том, что изображение будет соответствовать высоте экрана, а не заполнить ширину.
Я решил эту проблему, используя пользовательский ImageView, переопределив onDraw(Canvas)
и обработав его вручную с помощью холста; проблема с этим подходом заключается в том, что 1) я беспокоюсь, что может быть более простое решение, 2) я получаю исключение памяти виртуальной машины при вызове super(AttributeSet)
в конструкторе при попытке установить src img 330 КБ, когда в куче свободно 3 МБ (с размером кучи 6 мб) и не могу понять почему.
Любые идеи / предложения / решения приветствуются :)
Благодарность
ps Я подумал, что решением может быть использование типа матричной шкалы и сделать это сам, но, похоже, это такая же или большая работа, чем мое текущее решение!
Ответы:
Хорошо, у меня есть рабочее решение. Приглашение Дарко заставило меня снова взглянуть на класс ImageView (спасибо) и применить преобразование с помощью матрицы (как я изначально подозревал, но с первой попытки не увенчался успехом!). В моем пользовательском классе imageView я вызываю
setScaleType(ScaleType.MATRIX)
aftersuper()
в конструкторе и использую следующий метод.@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); }
Я поместил int в
setFrame()
метод, как и в ImageView, вызовconfigureBounds()
находится в этом методе, где происходит все масштабирование и матрица, поэтому мне кажется логичным (скажем, если вы не согласны)Ниже приведен метод super.setFrame () от 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; }
Найдите полный класс SRC здесь
источник
вот мой код для его центрирования внизу. Кстати. в коде Дори есть небольшая ошибка: поскольку метод
super.frame()
вызывается в самом конце,getWidth()
метод может вернуть неправильное значение. Если вы хотите центрировать его вверху, просто удалите строку postTranslate, и все готово. Приятно то, что с помощью этого кода вы можете перемещать его куда угодно. (справа, в центре => нет проблем;)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-l)
вместо этого?if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))
должна быть перевернута? Другими словами, не следует ли вам проверять, больше ли изображение, чем рамка? Предлагаю заменить его наif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
((View) getParent()).getWidth()
с тех пор, какImageView
hasMATCH_PARENT
Вам не нужно писать настраиваемое представление изображений для получения этой
TOP_CROP
функциональности. Вам просто нужно модифицироватьmatrix
изImageView
.Установите
scaleType
значениеmatrix
дляImageView
:<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"/>
Установите индивидуальную матрицу для
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);
Это даст вам
TOP_CROP
функциональность.источник
scaleType
вcenterCrop
противном случае я буду видеть пустое пространство по краям.Этот пример работает с изображениями, которые загружаются после создания объекта + некоторая оптимизация. Я добавил несколько комментариев в код, объясняющих, что происходит.
Не забудьте позвонить:
imageView.setScaleType(ImageView.ScaleType.MATRIX);
или
android:scaleType="matrix"
Источник 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 }
источник
На основе Дори я использую решение, которое либо масштабирует изображение в зависимости от ширины или высоты изображения, чтобы всегда заполнять окружающий контейнер. Это позволяет масштабировать изображение, чтобы заполнить все доступное пространство, используя верхнюю левую точку изображения, а не центр в качестве источника (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); }
Я надеюсь, что это поможет - работает как удовольствие в моем проекте.
источник
Ни одно из этих решений не сработало для меня, потому что мне нужен класс, поддерживающий произвольную обрезку в горизонтальном или вертикальном направлении, и я хотел, чтобы он позволял мне динамически изменять урожай. Мне также нужна была совместимость с Пикассо , а Пикассо лениво настраивал рисунки.
Моя реализация адаптирована непосредственно из ImageView.java в AOSP. Чтобы использовать это, объявите это в XML:
<com.yourapp.PercentageCropImageView android:id="@+id/view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix"/>
Из источника, если вы хотите получить лучший урожай, позвоните:
imageView.setCropYCenterOffsetPct(0f);
Если вы хотите получить нижний урожай, звоните:
imageView.setCropYCenterOffsetPct(1.0f);
Если вы хотите получить урожай на 1/3 длины вниз, позвоните:
imageView.setCropYCenterOffsetPct(0.33f);
Кроме того, если вы решите использовать другой метод кадрирования, например fit_center, вы можете это сделать, и никакая из этой настраиваемой логики не будет запущена. (Другие реализации позволяют использовать ТОЛЬКО их методы обрезки).
Наконец, я добавил метод redraw (), поэтому, если вы решите динамически изменять метод кадрирования / scaleType в коде, вы можете принудительно перерисовать представление. Например:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); fullsizeImageView.redraw();
Чтобы вернуться к своему индивидуальному кадру верхней центральной трети, позвоните:
fullsizeImageView.setScaleType(ScaleType.MATRIX); fullsizeImageView.redraw();
Вот класс:
/* * 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); } } }
источник
Возможно, войдите в исходный код для просмотра изображения на Android и посмотрите, как он рисует центральную часть кадра и т. Д., И, возможно, скопируйте часть этого кода в свои методы. Я действительно не знаю лучшего решения, чем это. У меня есть опыт ручного изменения размера и обрезки растрового изображения (поиск преобразований растрового изображения), что уменьшает его фактический размер, но по-прежнему создает небольшие накладные расходы в процессе.
источник
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); }
}
источник
onLayout
меня очень спасает! Благодарность! Я столкнулся с проблемой, когда он вычисляет матрицу, но не отображает изображение сразу, и добавлениеonLayout
кода решает мою проблему.Если вы используете Fresco (SimpleDraweeView), вы можете легко сделать это с помощью:
PointF focusPoint = new PointF(0.5f, 0f); imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
Этот будет для лучшего урожая.
Дополнительная информация по ссылке Ссылка
источник
Здесь есть 2 проблемы с решениями:
Эта небольшая модификация устраняет проблему (поместите код в onDraw и проверьте масштабные коэффициенты ширины и высоты):
@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); }
источник
Самое простое решение: обрезать изображение
@Override public void draw(Canvas canvas) { if(getWidth() > 0){ int clipHeight = 250; canvas.clipRect(0,clipHeight,getWidth(),getHeight()); } super.draw(canvas); }
источник