ImageView scaling top CROP
j'ai un ImageView
qui affiche un png qui a un plus grand rapport d'aspect que celui de l'appareil (verticalement parlant - ce qui signifie son plus long). Je veux afficher ceci tout en maintenant le rapport d'aspect, en faisant correspondre la largeur du parent, et en épinglant l'imageview au haut de l'écran.
Le problème que j'ai avec l'aide de CENTER_CROP
comme le type d'échelle est qu'il (comprendre) le centre de l'image à l'échelle au lieu d'aligner le bord supérieur vers le haut bord f l'affichage de l'image.
le problème avec FIT_START
est que l'image s'ajustera à la hauteur de l'écran et ne remplira pas la largeur.
j'ai résolu ce problème en utilisant un ImageView personnalisé et en écrasant onDraw(Canvas)
et en le remettant manuellement en utilisant la toile; le problème avec cette approche est que 1) je suis inquiet qu'il puisse y avoir une solution plus simple, 2) je reçois une exception VM mem lors de l'appel super(AttributeSet)
dans le constructeur en essayant de définir un src img De 330kb quand le tas a 3 Mo libres (avec une taille de tas de 6 Mo) et ne peut pas comprendre pourquoi.
Toutes les idées / suggestions / solutions sont les bienvenus :)
Merci
p. S. j'ai pensé qu'une solution pourrait être d'utiliser un type d'échelle de matrice et de le faire moi-même, mais cela semble être le même ou plus de travail que ma solution actuelle!
11 réponses
Ok, j'ai une solution. L'invite de Darko m'a fait regarder à nouveau à la classe ImageView (merci) et ont appliqué la transformation en utilisant une matrice (comme je l'ai d'abord suspecté mais n'a pas eu de succès sur ma première tentative!). Dans ma classe imageView personnalisée j'appelle setScaleType(ScaleType.MATRIX)
après super()
dans le constructeur, et j'ai la méthode suivante.
@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);
}
j'ai placé int dans la méthode setFrame()
comme dans ImageView l'appel à configureBounds()
est dans cette méthode, qui est l'endroit où toutes les choses de mise à l'échelle et de matrice ont lieu, donc semble logique pour moi (dire si vous n'êtes pas d'accord)
ci-dessous est le super.méthode 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;
}
trouver la classe complète src ici
voici mon code pour le centrage du bas. Btw. dans le Code de Dori il y a un petit bug: puisque le super.frame()
est appelé à la toute fin, la méthode getWidth()
pourrait retourner la mauvaise valeur. Si vous voulez le centrer en haut, retirez simplement la ligne postTranslate et c'est terminé. Ce qui est bien, c'est qu'avec ce code, vous pouvez le déplacer où vous voulez. (à droite, center => pas de problème ;)
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);
}
}
cet exemple fonctionne avec des images qui sont chargées après la création de l'objet + une certaine optimisation. J'ai ajouté des commentaires en code qui expliquent ce qui se passe.
rappelez-vous d'appeler:
imageView.setScaleType(ImageView.ScaleType.MATRIX);
ou
android:scaleType="matrix"
Java source:
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
}
vous n'avez pas besoin d'écrire une vue D'Image personnalisée pour obtenir la fonctionnalité TOP_CROP
. Vous avez juste besoin de modifier le matrix
du ImageView
.
-
mettre le
scaleType
àmatrix
pour leImageView
:<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"/>
-
définir une matrice personnalisée pour le
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);
faire ceci vous donnera la fonctionnalité TOP_CROP
.
basé sur Dori j'utilise une solution qui balance l'image en fonction de la largeur ou de la hauteur de l'image pour toujours remplir le conteneur environnant. Cela permet de mettre à l'échelle une image à remplir la totalité de l'espace disponible en utilisant le point supérieur gauche de l'image plutôt que le centre comme origine (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);
}
j'espère que cela aidera - fonctionne comme un plaisir dans mon projet.
aucune de ces solutions ne fonctionnait pour moi, parce que je voulais une classe qui supportait une récolte arbitraire soit horizontale soit verticale, et je voulais qu'elle me permette de changer la récolte de façon dynamique. J'avais aussi besoin de la compatibilité Picasso , et Picasso établit des tirages d'image paresseusement.
mon implémentation est adaptée directement de ImageView.java dans L'AOSP. Pour l'utiliser, déclarez comme ceci en XML:
<com.yourapp.PercentageCropImageView
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="matrix"/>
de la source, si vous souhaitez avoir une récolte haut, appelez:
imageView.setCropYCenterOffsetPct(0f);
si vous souhaitez avoir une récolte de fond, appelez:
imageView.setCropYCenterOffsetPct(1.0f);
si vous souhaitez avoir une récolte 1/3 de la descente, appelez:
imageView.setCropYCenterOffsetPct(0.33f);
de plus, si vous choisissez d'utiliser une autre méthode crop, comme fit_center, vous pouvez le faire et aucune de ces logiques personnalisées ne sera déclenchée. (Les autres implémentations ne vous permettent d'utiliser que leurs cultures méthode.)
enfin, j'ai ajouté une méthode, reddraw (), donc si vous choisissez de changer votre méthode crop/scaleType dynamiquement en code, vous pouvez forcer la vue à redessiner. Par exemple:
fullsizeImageView.setScaleType(ScaleType.FIT_CENTER);
fullsizeImageView.redraw();
pour revenir à votre culture Haut-centre-troisième personnalisé, appelez:
fullsizeImageView.setScaleType(ScaleType.MATRIX);
fullsizeImageView.redraw();
Voici la classe:
/*
* 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);
}
}
}
peut-être aller dans le code source pour la vue d'image sur android et voir comment il dessine la récolte centre, etc. et peut-être copier un peu de ce code dans vos méthodes. je ne sais pas vraiment une meilleure solution que de faire cela. j'ai l'expérience de redimensionner et recadrer manuellement le bitmap (rechercher des transformations bitmap) qui réduit sa taille réelle, mais il crée encore un peu d'un overhead dans le processus.
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);
}
}
il y a 2 problèmes avec les solutions ici:
- Elles ne rendent pas dans l'Android Studio de mise en page de l'éditeur (de sorte que vous pouvez avoir un aperçu sur les différentes tailles d'écran et les ratios d'aspect)
- il ne se balance que par la largeur, donc en fonction des rapports d'aspect de l'appareil et de l'image, vous pouvez finir avec une bande vide sur le fond
cette petite modification corrige le problème (placer le code dans onDraw, et vérifier facteurs d'échelle de largeur et de hauteur):
@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 vous utilisez Fresco (SimpleDraweeView) vous pouvez facilement le faire avec:
PointF focusPoint = new PointF(0.5f, 0f);
imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
celui-ci serait pour une culture supérieure.
plus d'informations à lien de référence
solution la plus simple: Clip l'image
@Override
public void draw(Canvas canvas) {
if(getWidth() > 0){
int clipHeight = 250;
canvas.clipRect(0,clipHeight,getWidth(),getHeight());
}
super.draw(canvas);
}