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!

77
demandé sur Dori 2011-06-13 15:56:55

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

83
répondu Dori 2014-08-10 13:01:47

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

    }
38
répondu MajorBreakfast 2018-09-11 20:22:07

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

}
24
répondu Jacek Marchwicki 2013-08-14 12:58:17

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 .

  1. mettre le scaleType à matrix pour le 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"/>
    
  2. 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 .

23
répondu Henry 2016-06-27 08:40:20

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.

12
répondu Matt 2013-02-18 20:41:43

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);
        }
    }
}
7
répondu esilver 2015-03-14 23:03:25

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.

5
répondu DArkO 2011-06-13 12:25:33
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);
}

}

2
répondu tianxia 2014-12-30 08:48:50

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);
}
0
répondu OldSchool4664 2016-12-07 23:05:11

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

0
répondu fxhereng 2017-09-01 10:14:04

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);
    }
-1
répondu M. Usman Khan 2015-12-05 06:31:36