web-dev-qa-db-fra.com

Mise à l'échelle ImageView TOP_CROP

J'ai un ImageView qui affiche un png qui a un rapport d'aspect plus grand que celui de l'appareil (verticalement parlant - ce qui signifie qu'il est plus long). Je souhaite l’afficher tout en conservant les proportions, en faisant correspondre la largeur du parent et en épinglant la vue en haut de l’écran. 

Le problème que j'ai avec l'utilisation de CENTER_CROP en tant que type d'échelle est qu'il va (compréhensible) centrer l'image redimensionnée au lieu d'aligner le bord supérieur sur le bord supérieur de la vue de l'image. 

Le problème avec FIT_START est que l’image correspondra à la hauteur de l’écran et ne remplira pas la largeur.

J'ai résolu ce problème en utilisant un ImageView personnalisé, en surchargeant onDraw(Canvas) et en le manipulant manuellement à l'aide du canevas; le problème avec cette approche est que 1) je crains une solution plus simple, 2) je reçois une exception VM lors de l’appel de super(AttributeSet) dans le constructeur lorsqu’on essaie de définir un img de 330kb src lorsque le tas a 3 Mo libre (avec une taille de tas de 6 Mo) et ne peut pas comprendre pourquoi.

Toutes les idées/suggestions/solutions sont les bienvenues :)

Merci

p.s. Je pensais qu'une solution pourrait être d'utiliser un type d'échelle matricielle et de le faire moi-même, mais cela semble être le même travail ou plus de travail que ma solution actuelle!

82
Dori

Ok, j'ai une solution de travail. L'invite de Darko m'a fait examiner de nouveau la classe ImageView (merci) et a appliqué la transformation à l'aide d'une matrice (comme je l'avais soupçonné à l'origine, mais je n'ai pas réussi à la première fois!). Dans ma classe imageView personnalisée, j'appelle setScaleType(ScaleType.MATRIX) après super() dans le constructeur et utilise 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, où toutes les opérations de mise à l'échelle et de matrice ont lieu, me semble donc logique (disons si vous n'êtes pas d'accord) 

Ci-dessous, la méthode 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;
    }

Retrouvez la classe complète src ici

83
Dori

voici mon code pour le centrer en bas. Btw. Le code de Dori est un petit bug: comme la fonction super.frame() est appelée à la toute fin, la méthode getWidth() peut renvoyer une valeur incorrecte. Si vous souhaitez le centrer en haut, supprimez simplement la ligne postTranslate et vous avez terminé. La bonne chose est qu'avec ce code, vous pouvez le déplacer où vous voulez. (droite, centre => 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);
        }

    }
40
MajorBreakfast

Vous n'avez pas besoin d'écrire une vue d'image personnalisée pour obtenir la fonctionnalité TOP_CROP. Il vous suffit de modifier la matrix de la ImageView.

  1. Définissez la scaleType sur matrix pour la 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éfinissez une matrice personnalisée pour la 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);
    

Cela vous donnera la fonctionnalité TOP_CROP.

28
Henry

Cet exemple fonctionne avec des images chargées après la création de l'objet + de l'optimisation ..__ J'ai ajouté des commentaires dans le code qui expliquent ce qui se passe.

N'oubliez pas d'appeler:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

ou

Android:scaleType="matrix"

Source 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

}
25
Jacek Marchwicki

Sur la base de Dori, j'utilise une solution qui redimensionne l'image en fonction de la largeur ou de la hauteur de l'image afin de toujours remplir le conteneur environnant. Cela permet de redimensionner une image pour remplir tout l'espace disponible en utilisant le point en haut à gauche de l'image plutôt que le centre en tant qu'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 aide - fonctionne comme un régal dans mon projet.

13
Matt

Aucune de ces solutions ne fonctionnait pour moi, car je voulais une classe qui prenne en charge un recadrage arbitraire, que ce soit horizontalement ou verticalement, et me permettre de modifier dynamiquement le recadrage. J'avais aussi besoin de Picasso compatibilité, et Picasso définit les images comme dessinables paresseusement.

Mon implémentation est adaptée directement depuis ImageView.Java dans l’APOS. 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"/>

À partir de la source, si vous souhaitez obtenir un recadrage supérieur, appelez: 

imageView.setCropYCenterOffsetPct(0f);

Si vous souhaitez avoir une récolte inférieure, appelez: 

imageView.setCropYCenterOffsetPct(1.0f);

Si vous souhaitez avoir un recadrage au tiers, appelez:

imageView.setCropYCenterOffsetPct(0.33f);

De plus, si vous choisissez d'utiliser une autre méthode de recadrage, telle que fit_center, vous pouvez le faire et aucune de cette logique personnalisée ne sera déclenchée. (D'autres implémentations vous permettent UNIQUEMENT d'utiliser leurs méthodes de rognage). 

Enfin, j'ai ajouté une méthode, redraw (), donc si vous choisissez de modifier dynamiquement votre méthode de rognage/scaleType dans le code, vous pouvez forcer la vue à redessiner. Par exemple:

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

Pour revenir à votre troisième culture personnalisée en haut au centre, 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
esilver

Peut-être allez-vous dans le code source pour l'affichage des images sur Android et voyez comment il dessine le rognage central, etc. Je ne sais pas vraiment pour une meilleure solution que celle-ci. J'ai l'expérience de redimensionner et de recadrer manuellement le bitmap (recherche de transformations de bitmap), ce qui réduit sa taille réelle mais crée néanmoins une légère surcharge dans le processus.

5
DArkO
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
tianxia

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 récolte supérieure.

Plus d'infos sur Lien de référence

0
fxhereng

Il y a 2 problèmes avec les solutions ici:

  • Ils ne s'affichent pas dans l'éditeur de disposition d'Android Studio (vous pouvez donc prévisualiser différentes tailles d'écran et différents formats).
  • La mise à l'échelle s'effectue uniquement en largeur. Ainsi, en fonction des formats d'image de l'appareil et de l'image, vous pouvez vous retrouver avec une bande vide en bas.

Cette petite modification corrige le problème (placez le code dans onDraw et vérifiez les 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
OldSchool4664