Escalado de ImageView TOP_CROP

Tengo un ImageView que muestra un png que tiene una relación de aspecto mayor que la del dispositivo (hablando verticalmente, lo que significa que es más larga). Quiero mostrar esto mientras mantengo la relación de aspecto, haciendo coincidir el ancho del elemento principal y fijando la vista de la imagen en la parte superior de la pantalla.

El problema que tengo con el uso de CENTER_CROP como el 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 la imagen.

El problema con 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 manipulándolo manualmente utilizando el canvas; el problema con este enfoque es que 1) me preocupa que pueda haber una solución más simple, 2) obtengo una excepción de memoría de máquina virtual al llamar a super(AttributeSet) en el constructor cuando bash establecer una img src de 330 kb cuando el montón tiene 3 mb gratis (con un tamaño de stack de 6 mb) y no puedo entender por qué.

Cualquier idea / sugerencia / solución es muy bienvenida 🙂

Gracias

ps, pensé que una solución podría ser usar un tipo de escala de matriz y hacerlo yo mismo, ¡pero parece ser el mismo o más trabajo que mi solución actual!

Ok, tengo una solución de trabajo. El mensaje de Darko me hizo volver a mirar la clase ImageView (gracias) y he aplicado la transformación usando una matriz (como sospeché originalmente pero no tuve éxito en mi primer bash). En mi clase personalizada de imageView llamo a setScaleType(ScaleType.MATRIX) después de super() 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 método setFrame() ya que en ImageView la llamada a configureBounds() está dentro de este método, que es donde tienen lugar todos los elementos de escalado y matriz, por lo que me parece lógico (por ejemplo, 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; } 

Encuentre el src de clase completa aquí

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 se llama al super.frame() al final, el método getWidth() puede devolver el valor incorrecto. Si quieres centrarlo en la parte superior simplemente elimina la línea de 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) { 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); } } 

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.

Recuerde 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(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 } 

No necesita escribir una vista de imagen personalizada para obtener la funcionalidad TOP_CROP . Solo necesita modificar la matrix de ImageView .

  1. Establezca scaleType en la matrix para ImageView :

      
  2. 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 funcionalidad TOP_CROP .

Basado en Dori estoy usando una solución que escala la imagen en función del ancho o alto de la imagen para llenar siempre el contenedor circundante. Esto permite escalar una imagen para llenar todo el espacio disponible utilizando 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.

Ninguna de estas soluciones funcionó para mí, porque quería una clase que admitiera un cultivo arbitrario desde la dirección horizontal o vertical, y quería que me permitiera cambiar el cultivo de forma dinámica. También necesitaba compatibilidad con Picasso , y Picasso establece la imagen arrastrada perezosamente.

Mi implementación se adapta directamente desde ImageView.java en el AOSP. Para usarlo, declara como tal en XML:

   

Desde el origen, si desea tener un cultivo superior, llame a:

 imageView.setCropYCenterOffsetPct(0f); 

Si desea tener un cultivo inferior, llame a:

 imageView.setCropYCenterOffsetPct(1.0f); 

Si deseas tener un cultivo 1/3 del camino hacia abajo, llama:

 imageView.setCropYCenterOffsetPct(0.33f); 

Además, si elige usar otro método de recorte, como fit_center, puede hacerlo y no se activará ninguna de esta lógica personalizada. (Otras implementaciones SÓLO le permiten usar sus métodos de cultivo).

Por último, agregué un método, redraw (), así que si elige cambiar su método de recorte / scaleType dinámicamente en el código, puede forzar la vista para volver a dibujar. Por ejemplo:

 fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); fullsizeImageView.redraw(); 

Para volver a su cultivo personalizado en el tercio 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); } } } 

Tal vez vaya al código fuente de la vista de imagen en Android y vea cómo dibuja el centro de cultivo, etc. y quizás copie parte de ese código en sus métodos. Realmente no sé si hay una solución mejor que hacer esto. Tengo experiencia en redimensionar y recortar manualmente el bitmap (buscar transformaciones de bitmap), lo que reduce su tamaño real, pero aún crea un poco de sobrecarga en el proceso.

 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); } 

}

Hay 2 problemas con las soluciones aquí:

  • No se procesan en el editor de diseño de Android Studio (para que pueda obtener una vista previa de varios tamaños de pantalla y relaciones de aspecto)
  • Solo se puede ampliar por ancho, por lo que dependiendo de las relaciones de aspecto del dispositivo y la imagen, puede terminar con una tira vacía en la parte inferior

Esta pequeña modificación corrige el problema (coloque el código en onDraw y verifique los factores de escala de ancho y altura):

 @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); } 

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 Reference Link

La solución más simple: recorte la imagen

  @Override public void draw(Canvas canvas) { if(getWidth() > 0){ int clipHeight = 250; canvas.clipRect(0,clipHeight,getWidth(),getHeight()); } super.draw(canvas); }