Tengo un ImageView
que muestra un png que tiene una relación de aspecto más grande que la del dispositivo (verticalmente hablando, lo que significa que es más largo). Quiero mostrar esto manteniendo la relación de aspecto, haciendo coincidir el ancho del padre y fijando la vista de la imagen en la parte superior de la pantalla.
El problema que tengo con el uso CENTER_CROP
como tipo de escala es que (comprensible) centrará la imagen escalada en lugar de alinear el borde superior con el borde superior de la vista de imagen.
El problema FIT_START
es que la imagen se ajustará a la altura de la pantalla y no llenará el ancho.
He resuelto este problema utilizando un ImageView personalizado y anulando onDraw(Canvas)
y manejando esto manualmente usando el lienzo; el problema con este enfoque es que 1) me preocupa que pueda haber una solución más simple, 2) super(AttributeSet)
obtengo una excepción VM mem cuando llamo al constructor cuando intento establecer un src img de 330kb cuando el montón tiene 3 mb libres (con un tamaño de pila de 6 mb) y no puedo entender por qué.
Cualquier idea / sugerencia / solución es bienvenida :)
Gracias
PD: pensé que una solución podría ser usar un tipo de escala matricial y hacerlo yo mismo, ¡pero parece ser el mismo o más trabajo que mi solución actual!
Respuestas:
Ok, tengo una solución que funciona. El mensaje de Darko me hizo mirar nuevamente a la clase ImageView (gracias) y aplicar la transformación usando una Matriz (como sospeché originalmente, ¡pero no tuve éxito en mi primer intento!). En mi clase imageView personalizada, llamo
setScaleType(ScaleType.MATRIX)
despuéssuper()
en el constructor y tengo el siguiente método.@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); }
He colocado int en el
setFrame()
método como en ImageView, la llamada aconfigureBounds()
está dentro de este método, que es donde se llevan a cabo todas las cosas de escala y matriz, por lo que me parece lógico (diga si no está de acuerdo)A continuación se muestra el método super.setFrame () de 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; }
Encuentra la clase completa src aquí
fuente
aquí está mi código para centrarlo en la parte inferior. Por cierto. en el Código de Dori hay un pequeño error: dado que
super.frame()
se llama al final, elgetWidth()
método puede devolver un valor incorrecto. Si desea centrarlo en la parte superior, simplemente elimine la línea postTranslate y listo. Lo bueno es que con este código puedes moverlo a donde quieras. (derecha, centro => no hay problema;)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); } }
fuente
(r-l)
en su lugar?if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))
debería invertirse? En otras palabras, ¿no debería probar si la imagen es más grande que el marco? Sugiero reemplazarlo conif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
((View) getParent()).getWidth()
ImageView
MATCH_PARENT
No es necesario que escriba una vista de imagen personalizada para obtener la
TOP_CROP
funcionalidad. Solo necesita modificar elmatrix
deImageView
.Establezca
scaleType
enmatrix
paraImageView
:<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"/>
Establezca una matriz personalizada para
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);
Hacer esto le dará la
TOP_CROP
funcionalidad.fuente
scaleType
a, de locenterCrop
contrario, veré un espacio en blanco en los bordes.Este ejemplo funciona con imágenes que se cargan después de la creación del objeto + alguna optimización. Agregué algunos comentarios en el código que explican lo que está sucediendo.
Recuerda llamar:
imageView.setScaleType(ImageView.ScaleType.MATRIX);
o
android:scaleType="matrix"
Fuente de 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 }
fuente
Basado en Dori, estoy usando una solución que escala la imagen según el ancho o la altura de la imagen para llenar siempre el contenedor circundante. Esto permite escalar una imagen para llenar todo el espacio disponible usando el punto superior izquierdo de la imagen en lugar del centro como origen (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); }
Espero que esto ayude, funciona como un regalo en mi proyecto.
fuente
Ninguna de estas soluciones funcionó para mí, porque quería una clase que admitiera un recorte arbitrario desde la dirección horizontal o vertical, y quería que me permitiera cambiar el recorte de forma dinámica. También necesitaba compatibilidad con Picasso , y Picasso establece los dibujables de imágenes con pereza.
Mi implementación está adaptada directamente de ImageView.java en el AOSP. Para usarlo, declare así en XML:
<com.yourapp.PercentageCropImageView android:id="@+id/view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix"/>
Desde la fuente, si desea tener una cosecha superior, llame a:
imageView.setCropYCenterOffsetPct(0f);
Si desea tener un cultivo de fondo, llame a:
imageView.setCropYCenterOffsetPct(1.0f);
Si desea tener un recorte de 1/3 del camino hacia abajo, llame a:
imageView.setCropYCenterOffsetPct(0.33f);
Además, si elige utilizar otro método de recorte, como fit_center, puede hacerlo y no se activará ninguna de esta lógica personalizada. (Otras implementaciones SOLAMENTE le permiten usar sus métodos de recorte).
Por último, agregué un método, redraw (), por lo que si elige cambiar su método de recorte / scaleType dinámicamente en el código, puede forzar que la vista se vuelva a dibujar. Por ejemplo:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); fullsizeImageView.redraw();
Para volver a su tercer cultivo personalizado en la parte superior central, llame a:
fullsizeImageView.setScaleType(ScaleType.MATRIX); fullsizeImageView.redraw();
Aquí está la clase:
/* * 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); } } }
fuente
Tal vez vaya al código fuente para la vista de imagen en Android y vea cómo dibuja el recorte central, etc. y tal vez copie algo de ese código en sus métodos. Realmente no conozco una mejor solución que hacer esto. Tengo experiencia en cambiar el tamaño y recortar manualmente el mapa de bits (búsqueda de transformaciones de mapa de bits), lo que reduce su tamaño real, pero aún crea un poco de sobrecarga en el proceso.
fuente
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); }
}
fuente
onLayout
me salva mucho! ¡Gracias! Encontré un problema en el que calcula la matriz pero no muestra la imagen inmediatamente y agregaronLayout
el código resuelve mi problema.Si está utilizando Fresco (SimpleDraweeView) , puede hacerlo fácilmente con:
PointF focusPoint = new PointF(0.5f, 0f); imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
Este sería para una cosecha superior.
Más información en Enlace de referencia
fuente
Hay 2 problemas con las soluciones aquí:
Esta pequeña modificación soluciona el problema (coloque el código en onDraw y verifique los factores de escala de ancho y alto):
@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); }
fuente
Solución más simple: recorta la imagen
@Override public void draw(Canvas canvas) { if(getWidth() > 0){ int clipHeight = 250; canvas.clipRect(0,clipHeight,getWidth(),getHeight()); } super.draw(canvas); }
fuente