Масштабирование ImageView TOP_CROP
У меня есть ImageView
который отображает png, который имеет больший формат изображения, чем у устройства (по вертикали, что означает его дольше). Я хочу отображать это, сохраняя соотношение сторон, сопоставляя ширину родительского элемента и привязывая изображение к верхней части экрана.
Проблема, с которой я использую CENTER_CROP
в качестве типа шкалы, заключается в том, что она (понятная) CENTER_CROP
масштабированное изображение, а не выравнивает верхний край до верхнего края f изображения.
Проблема с FIT_START
заключается в том, что изображение будет соответствовать высоте экрана и не заполнять ширину.
- Полноэкранное фоновое изображение в действии
- Пользовательский ImageView с тенью
- Как сделать ImageView с закругленными углами?
- Android: прокрутка изображения
- Как получить ширину и высоту изображения в андроиде?
Я решил эту проблему, используя пользовательский ImageView и переопределяя onDraw(Canvas)
и обрабатывая это вручную, используя canvas; проблема с этим подходом заключается в том, что 1) я беспокоюсь, что может быть более простое решение, 2) Я получаю исключение VM mem при вызове super(AttributeSet)
в конструкторе при попытке установить src img 330kb, когда куча имеет 3 мб бесплатно (с размером кучи 6 мб) и не может решить, почему.
Любые идеи / предложения / решения приветствуются 🙂
благодаря
ps, я думал, что решение может состоять в том, чтобы использовать тип матричного масштаба и делать это самостоятельно, но это похоже на то же или больше, чем мое текущее решение!
- Заменить селекторные изображения программно
- Изменение образа ImageView программно в Android
- Попытка получить размер изображения в ImageView
- Маска ImageView с круглым фоном угла
- Как выровнять TextView вокруг ImageView?
- Проблема с реальным устройством с использованием векторного изображения в android. SVG-андроид
- ImageView - квадрат с динамической шириной?
- Android Текст поверх изображения
Хорошо, у меня есть рабочее решение. Приглашение от Darko заставило меня снова взглянуть на class ImageView (спасибо) и применил преобразование, используя Matrix (как я подозревал, но не имел успеха при первой попытке!). В моем classе imageView я вызываю setScaleType(ScaleType.MATRIX)
после super()
в конструкторе и имею следующий метод.
@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; }
Найти полный class src здесь
вот мой код для его центрирования внизу. Btw. в коде Дори есть небольшая ошибка: поскольку 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) { 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); } }
Этот пример работает с изображениями, загружаемыми после создания объекта + некоторая оптимизация. Я добавил несколько комментариев в код, которые объясняют, что происходит.
Не забудьте позвонить:
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(rl, bt); } 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 }
Вам не нужно писать пользовательский вид изображения для получения функциональности TOP_CROP
. Вам просто нужно изменить matrix
ImageView
.
-
Установите
scaleType
вmatrix
дляImageView
: -
Задайте настраиваемую матрицу для
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
.
На основе Dori я использую решение, которое либо масштабирует изображение в зависимости от ширины или высоты изображения, чтобы всегда заполнять окружающий контейнер. Это позволяет масштабировать изображение для заполнения всего доступного пространства, используя верхнюю левую точку изображения, а не центр как источник (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); }
Надеюсь, что это помогает – работает как в моем проекте.
Ни один из этих решений не работал для меня, потому что мне нужен class, который поддерживал произвольный урожай с горизонтального или вертикального направления, и я хотел, чтобы он позволял мне динамически изменять культуру. Мне также нужна совместимость с Пикассо , и Пикассо лениво устанавливает образы изображений.
Моя реализация адаптирована непосредственно из ImageView.java в AOSP. Чтобы использовать его, объявите так в XML:
Из источника, если вы хотите иметь верхний кадр, вызовите:
imageView.setCropYCenterOffsetPct(0f);
Если вы хотите получить снизу, вызовите:
imageView.setCropYCenterOffsetPct(1.0f);
Если вы хотите получить урожай на 1/3 пути вниз, вызовите:
imageView.setCropYCenterOffsetPct(0.33f);
Кроме того, если вы решите использовать другой метод обрезки, например fit_center, вы можете сделать это, и ни одна из этих пользовательских логик не будет запущена. (Другие реализации ТОЛЬКО позволяют использовать методы обрезки).
Наконец, я добавил метод redraw (), поэтому, если вы решите изменить свой метод обрезки / scaleType динамически в коде, вы можете заставить перерисовать представление. Например:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); fullsizeImageView.redraw();
Чтобы вернуться к своему пользовательскому первоclassному третьему уроку, вызовите:
fullsizeImageView.setScaleType(ScaleType.MATRIX); fullsizeImageView.redraw();
Вот class:
/* * 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); }
}
Здесь есть две проблемы:
- Они не отображаются в редакторе макетов Android Studio (поэтому вы можете просматривать различные размеры и пропорции экрана)
- Он масштабируется только по ширине, поэтому в зависимости от соотношения сторон устройства и изображения вы можете получить пустую полосу на дне
Эта небольшая модификация устраняет проблему (код места в 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); }
Если вы используете Fresco (SimpleDraweeView), вы можете легко сделать это с помощью:
PointF focusPoint = new PointF(0.5f, 0f); imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
Это было бы для высшего урожая.
Дополнительная информация по ссылке Ссылка
Простейшее решение: закрепите изображение
@Override public void draw(Canvas canvas) { if(getWidth() > 0){ int clipHeight = 250; canvas.clipRect(0,clipHeight,getWidth(),getHeight()); } super.draw(canvas); }