Ridimensionamento ImageView TOP_CROP


88

Ho un ImageViewche sta visualizzando un png che ha un rapporto di aspetto più grande di quello del dispositivo (verticalmente parlando, il che significa che è più lungo). Voglio visualizzarlo mantenendo le proporzioni, facendo corrispondere la larghezza del genitore e bloccando l'immagine nella parte superiore dello schermo.

Il problema che ho con l'utilizzo CENTER_CROPcome tipo di scala è che (comprensibile) centrerà l'immagine scalata invece di allineare il bordo superiore al bordo superiore della vista dell'immagine.

Il problema FIT_STARTè che l'immagine si adatterà all'altezza dello schermo e non riempirà la larghezza.

Ho risolto questo problema utilizzando un ImageView personalizzato e sovrascrivendolo onDraw(Canvas)e gestendolo manualmente utilizzando la tela; il problema con questo approccio è che 1) sono preoccupato che possa esserci una soluzione più semplice, 2) ricevo un'eccezione VM mem quando chiamo super(AttributeSet)il costruttore quando provo a impostare un src img di 330kb quando l'heap ha 3 mb liberi (con una dimensione dell'heap di 6 MB) e non riesco a capire perché.

Eventuali idee / suggerimenti / soluzioni sono i benvenuti :)

Grazie

ps ho pensato che una soluzione potrebbe essere quella di utilizzare un tipo di scala a matrice e farlo da solo, ma sembra essere lo stesso o più lavoro della mia soluzione attuale!


1
Hai provato con CENTER_CROP e hai impostato la proprietà AdjustViewBounds come true con ImageView?
PravinCG

2
Sì, l'ho provato, non ho avuto successo, ho paura in quanto espanderà la vista fino a quando non si allargherà al suo genitore, che non sarà più grande dello schermo, quindi centrerà l'immagine sullo schermo con l'altezza in eccesso / 2 che sporge dalla parte superiore e in basso
Dori

Risposte:


84

Ok, ho una soluzione funzionante. Il prompt di Darko mi ha fatto guardare di nuovo la classe ImageView (grazie) e ho applicato la trasformazione utilizzando una matrice (come inizialmente sospettavo ma non ho avuto successo al primo tentativo!). Nella mia classe imageView personalizzata chiamo setScaleType(ScaleType.MATRIX)after super()nel costruttore e ho il seguente metodo.

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

Ho inserito int nel setFrame()metodo come in ImageView la chiamata a configureBounds()è all'interno di questo metodo, che è dove si svolgono tutte le cose di ridimensionamento e matrice, quindi mi sembra logico (dì se non sei d'accordo)

Di seguito è riportato il metodo super.setFrame () di 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;
    }

Trova il corso completo src qui


Grazie per il codice, @doridori! Ha funzionato bene! Solo non capisco perché hai ripetuto il metodo "setFrame" nella tua spiegazione ... Ho usato solo il primo con successo (e ho completamente ignorato il secondo xD)
Alesqui

3
Dopo aver combattuto con questo tramite il layout xml per due ore, ha funzionato. Vorrei poterti dare più barche.
Mark Beaton

5
Ho dovuto chiamare super () prima del corpo altrimenti l'immagine non sarebbe stata visualizzata senza ridipingere
sherpya

1
@VitorHugoSchwaab devi usare thms come matrix.postTranslate (..)
Anton Kizema

1
non puoi usare lo sfondo? usa solo src?
Egos Zhang

43

ecco il mio codice per centrarlo in basso. Btw. nel codice di Dori c'è un piccolo bug: poiché super.frame()viene chiamato proprio alla fine, il getWidth()metodo potrebbe restituire il valore sbagliato. Se vuoi centrarlo in alto rimuovi semplicemente la riga postTranslate e il gioco è fatto. La cosa bella è che con questo codice puoi spostarlo ovunque tu voglia. (a destra, al centro => nessun 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);
        }

    }

Ottimo, grazie per aver segnalato il bug. Non ho toccato questo codice per un po ', quindi questo potrebbe essere un suggerimento stupido, ma per correggere il bug di setWidth che hai indicato non si potrebbe semplicemente usare (r-l)?
Dori

Sicuramente la linea if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))dovrebbe essere invertita? In altre parole, non dovresti testare se l'immagine è più grande della cornice? Suggerisco di sostituirlo conif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P

Ho scelto la larghezza dal genitore utilizzando da ((View) getParent()).getWidth()quando ImageViewhaMATCH_PARENT
sherpya

@Jay funziona alla grande. Sto facendo calcoli di matrici nel metodo onLayout (), che viene chiamato anche in rotazione, mentre setFrame no.
box

Grazie per questo, funziona perfettamente, e anche grazie per aver dato un modo per cambiare rapidamente il ritaglio dal basso al ritaglio superiore commentando 1 riga di codice
Moonbloom

39

Non è necessario scrivere una visualizzazione immagine personalizzata per ottenere la TOP_CROPfunzionalità. Hai solo bisogno di modificare il matrixdi ImageView.

  1. Imposta scaleTypesu matrixper 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. Imposta una matrice personalizzata per 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);

In questo modo otterrai la TOP_CROPfunzionalità.


2
Questo ha funzionato per me. Tuttavia, devo controllare scaleRation se è <1, quindi cambio semplicemente scaleTypein centerCropaltrimenti vedrò uno spazio vuoto sui bordi.
Arst

Come farlo funzionare per i casi in cui sono necessarie immagini allineate in basso?
Sagar

26

Questo esempio funziona con le immagini che vengono caricate dopo la creazione dell'oggetto + alcune ottimizzazioni. Ho aggiunto alcuni commenti nel codice che spiegano cosa sta succedendo.

Ricordati di chiamare:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

o

android:scaleType="matrix"

Fonte 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

}

Come farlo funzionare per i casi in cui sono necessarie immagini allineate in basso?
Sagar

13

Basato su Dori, sto usando una soluzione che ridimensiona l'immagine in base alla larghezza o all'altezza dell'immagine per riempire sempre il contenitore circostante. Ciò consente di ridimensionare un'immagine per riempire l'intero spazio disponibile utilizzando il punto in alto a sinistra dell'immagine anziché il centro come 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);
}

Spero che questo aiuti - funziona come un regalo nel mio progetto.


11
Questa è la soluzione migliore ... E aggiungi: float width = r - l; altezza del galleggiante = b - t;
Geltrude

9

Nessuna di queste soluzioni ha funzionato per me, perché volevo una classe che supportasse un ritaglio arbitrario dalla direzione orizzontale o verticale e volevo che mi permettesse di cambiare il ritaglio in modo dinamico. Avevo anche bisogno della compatibilità con Picasso , e Picasso imposta pigramente i drawables delle immagini.

La mia implementazione è adattata direttamente da ImageView.java in AOSP. Per usarlo, dichiara così in XML:

    <com.yourapp.PercentageCropImageView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="matrix"/>

Dalla fonte, se desideri avere un raccolto superiore, chiama:

imageView.setCropYCenterOffsetPct(0f);

Se desideri avere un crop bottom, chiama:

imageView.setCropYCenterOffsetPct(1.0f);

Se desideri ottenere un ritaglio di 1/3 della discesa, chiama:

imageView.setCropYCenterOffsetPct(0.33f);

Inoltre, se scegli di utilizzare un altro metodo di ritaglio, come fit_center, puoi farlo e nessuna di questa logica personalizzata verrà attivata. (SOLO altre implementazioni ti consentono di utilizzare i loro metodi di ritaglio).

Infine, ho aggiunto un metodo, redraw (), quindi se scegli di cambiare il metodo di ritaglio / scaleType dinamicamente nel codice, puoi forzare il ridisegno della vista. Per esempio:

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

Per tornare al terzo ritaglio personalizzato in alto al centro, chiama:

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

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

5

Magari vai nel codice sorgente per la visualizzazione dell'immagine su Android e guarda come disegna il ritaglio centrale ecc. E magari copia parte di quel codice nei tuoi metodi. Non so davvero per una soluzione migliore che farlo. Ho esperienza nel ridimensionare e ritagliare manualmente la bitmap (ricerca di trasformazioni bitmap) che riduce le sue dimensioni effettive ma crea comunque un po 'di overhead nel processo.


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

}


setFrame && onLayout
tianxia

computMatrix: puoi fare qualsiasi matrice qui.
tianxia

onLayoutmi fa risparmiare molto! Grazie! Ho riscontrato un problema in cui calcola la matrice ma non visualizza immediatamente l'immagine e l'aggiunta onLayoutdel codice risolve il mio problema.
natsumiyu

1

Se stai usando Fresco (SimpleDraweeView) puoi farlo facilmente con:

 PointF focusPoint = new PointF(0.5f, 0f);
 imageDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);

Questo sarebbe per un raccolto superiore.

Maggiori informazioni al link di riferimento


0

Ci sono 2 problemi con le soluzioni qui:

  • Non vengono visualizzati nell'editor di layout di Android Studio (quindi puoi visualizzare l'anteprima su varie dimensioni dello schermo e proporzioni)
  • Si ridimensiona solo in base alla larghezza, quindi a seconda delle proporzioni del dispositivo e dell'immagine, puoi finire con una striscia vuota sul fondo

Questa piccola modifica risolve il problema (inserisci il codice in onDraw e controlla i fattori di scala di larghezza e altezza):

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

-1

Soluzione più semplice: ritaglia l'immagine

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

Questo non ridimensionerà l'immagine se è più piccola della vista, motivo per cui le altre soluzioni non sono così semplici.
OldSchool4664
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.