Масштабирование ImageView TOP_CROP
У меня есть ImageView
, который отображает png, который имеет больший формат изображения, чем у устройства (по вертикали - это означает его дольше). Я хочу отображать это, сохраняя соотношение сторон, сопоставляя ширину родительского элемента и привязывая изображение к верхней части экрана.
Проблема с использованием CENTER_CROP
в качестве типа шкалы заключается в том, что она (понятная) центрирует масштабированное изображение, а не выравнивает верхний край до верхнего края f изображения.
Проблема с FIT_START
заключается в том, что изображение будет соответствовать высоте экрана и не будет заполнять ширину.
Я решил эту проблему, используя пользовательский ImageView и переопределяя onDraw(Canvas)
и обрабатывая это вручную с помощью холста; проблема с этим подходом заключается в том, что 1) я беспокоюсь, что может быть более простое решение, 2) Я получаю исключение VM mem при вызове super(AttributeSet)
в конструкторе при попытке установить src img 330kb, когда куча имеет 3 мб бесплатно (с размером кучи 6 мб) и не может решить, почему.
Любые идеи/предложения/решения приветствуются:)
Спасибо
p.s. Я думал, что решение может состоять в том, чтобы использовать тип матричной шкалы и делать это самостоятельно, но это похоже на то же или больше, чем мое текущее решение!
Ответы
Ответ 1
Хорошо, у меня есть рабочее решение. Приглашение от Darko заставило меня снова взглянуть на класс ImageView (спасибо) и применил преобразование, используя Matrix (как я подозревал, но не имел успеха при первой попытке!). В моем классе 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;
}
Найти полный класс src здесь
Ответ 2
вот мой код для его центрирования внизу. Btw. в коде Dori это небольшая ошибка: поскольку 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);
}
}
Ответ 3
Этот пример работает с изображениями, загружаемыми после создания объекта + некоторой оптимизации.
Я добавил несколько комментариев в код, которые объясняют, что происходит.
Не забудьте позвонить:
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
}
Ответ 4
Вам не нужно писать пользовательский вид изображения для получения функциональности 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
.
Ответ 5
На основе 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);
}
Я надеюсь, что это поможет - работает как удовольствие в моем проекте.
Ответ 6
Ни одно из этих решений не работало для меня, потому что мне нужен класс, который поддерживал произвольный урожай с горизонтального или вертикального направления, и я хотел, чтобы он позволял мне динамически изменять культуру. Мне также нужна Picasso, а Picasso лениво устанавливает образы изображений.
Моя реализация адаптирована непосредственно из 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 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 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);
}
}
}
Ответ 7
Возможно, зайдите в исходный код для просмотра изображения на Android и посмотрите, как он рисует центральную обрезку и т.д. и, возможно, скопируйте часть этого кода в свои методы. я действительно не знаю, для лучшего решения, чем для этого. У меня есть опыт вручную изменять размер и обрезать растровое изображение (поиск преобразований растровых изображений), который уменьшает его фактический размер, но он все еще создает немного служебных данных в процессе.
Ответ 8
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);
}
}
Ответ 9
Здесь есть две проблемы:
- Они не отображаются в редакторе макета 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);
}
Ответ 10
Если вы используете Fresco (SimpleDraweeView), вы можете легко сделать это с помощью
PointF focusPoint = new PointF(0.5f, 0f);
imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
Это будет для верхнего урожая.
Дополнительная информация на Ссылка Ссылка
Ответ 11
Простейшее решение: закрепите изображение
@Override
public void draw(Canvas canvas) {
if(getWidth() > 0){
int clipHeight = 250;
canvas.clipRect(0,clipHeight,getWidth(),getHeight());
}
super.draw(canvas);
}