it-swarm.com.de

ImageView-Skalierung TOP_CROP

Ich habe eine ImageView, die ein PNG anzeigt, das ein größeres Seitenverhältnis als das des Geräts hat (vertikal gesprochen - was bedeutet, dass es länger ist). Ich möchte dies unter Beibehaltung des Seitenverhältnisses anzeigen, die Breite des übergeordneten Elements anpassen und die Bildansicht an den oberen Rand des Bildschirms pinnen. 

Das Problem, das ich bei der Verwendung von CENTER_CROP als Skalentyp habe, besteht darin, dass das skalierte Bild (verständlich) zentriert wird, anstatt den oberen Rand am oberen Rand der Bildansicht auszurichten. 

Das Problem bei FIT_START ist, dass das Bild der Bildschirmhöhe entspricht und nicht die Breite ausfüllt.

Ich habe dieses Problem gelöst, indem ich ein benutzerdefiniertes ImageView verwendet und onDraw(Canvas) überschreibe und dieses manuell mit der Leinwand handle. Das Problem bei diesem Ansatz ist, dass 1) ich bin besorgt, dass es möglicherweise eine einfachere Lösung gibt, 2) ich eine Mem-Ausnahme vom Typ VM erhalte, wenn im Konstruktor super(AttributeSet) aufgerufen wird, wenn versucht wird, ein src-img von 330kb festzulegen Heap hat 3 MB frei (mit einer Heap-Größe von 6 MB) und kann nicht herausfinden, warum.

Irgendwelche Ideen/Vorschläge/Lösungen sind herzlich willkommen :)

Vielen Dank

p.s. Ich dachte, dass eine Lösung darin bestehen könnte, einen Matrix-Skalentyp zu verwenden und ihn selbst auszuführen, aber das scheint die gleiche oder mehr Arbeit zu sein als meine derzeitige Lösung!

82
Dori

Ok, ich habe eine funktionierende Lösung. Die Aufforderung von Darko brachte mich dazu, die ImageView-Klasse erneut zu betrachten (Danke) und habe die Transformation mithilfe einer Matrix angewendet (wie ich ursprünglich vermutete, aber beim ersten Versuch keinen Erfolg hatte!). In meiner benutzerdefinierten imageView-Klasse rufe ich setScaleType(ScaleType.MATRIX) nach super() im Konstruktor auf und habe die folgende Methode.

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

Ich habe int in die setFrame()-Methode gestellt, da in ImageView der Aufruf von configureBounds() innerhalb dieser Methode erfolgt. Hier findet das gesamte Skalierungs- und Matrix-Material statt. 

Unten ist die super.setFrame () -Methode von 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;
    }

Hier finden Sie die vollständige Klasse src hier

83
Dori

hier ist mein Code zum Zentrieren unten. Übrigens In Dori's Code ist ein kleiner Fehler: Da die super.frame() ganz am Ende aufgerufen wird, gibt die getWidth()-Methode möglicherweise den falschen Wert zurück. Wenn Sie es oben zentrieren möchten, entfernen Sie einfach die postTranslate-Zeile und Sie sind fertig. Das Schöne daran ist, dass Sie diesen Code mit diesem Code beliebig verschieben können. (rechts, zentriert => kein Problem;)

    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

Sie müssen keine benutzerdefinierte Bildansicht schreiben, um die TOP_CROP-Funktionalität zu erhalten. Sie müssen lediglich die matrix der ImageView ändern.

  1. Setzen Sie scaleType für matrix auf 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. Legen Sie eine benutzerdefinierte Matrix für ImageView fest:

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

Dadurch erhalten Sie die TOP_CROP-Funktionalität.

28
Henry

Dieses Beispiel funktioniert mit Bildern, die nach der Erstellung des Objekts geladen werden, sowie einige Optimierung .. Ich habe im Code einige Kommentare hinzugefügt, die erklären, was los ist.

Denken Sie daran, anzurufen:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

oder

Android:scaleType="matrix"

Java-Quelle:

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

Basierend auf Dori verwende ich eine Lösung, die das Bild entweder anhand der Breite oder Höhe des Bildes skaliert, um immer den umgebenden Behälter zu füllen. Dies ermöglicht die Skalierung eines Bildes auf den gesamten verfügbaren Platz ausfüllen, wobei der obere linke Punkt des Bildes verwendet wird und nicht der Mittelpunkt als Ursprung (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);
}

Ich hoffe das hilft - funktioniert in meinem Projekt wie ein Leckerbissen.

13
Matt

Keine dieser Lösungen funktionierte für mich, weil ich eine Klasse wollte, die einen beliebigen Schnitt aus horizontaler oder vertikaler Richtung unterstützt, und ich wollte, dass ich den Schnitt dynamisch ändern kann. Ich brauchte auch Picasso - Kompatibilität, und Picasso legt die Bildskizzen träge fest.

Meine Implementierung wird direkt von ImageView.Java im AOSP angepasst. Um es zu verwenden, deklarieren Sie es wie in XML:

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

Wenn Sie eine Top-Ernte wünschen, rufen Sie Folgendes an: 

imageView.setCropYCenterOffsetPct(0f);

Wenn Sie einen Grundschnitt wünschen, rufen Sie an: 

imageView.setCropYCenterOffsetPct(1.0f);

Wenn Sie ein Erntegut von 1/3 des Weges wünschen, rufen Sie an:

imageView.setCropYCenterOffsetPct(0.33f);

Wenn Sie sich für eine andere Zuschneidemethode wie fit_center entscheiden, können Sie dies auch tun und keine dieser benutzerdefinierten Logik wird ausgelöst. (Bei anderen Implementierungen können Sie NUR die entsprechenden Beschneidungsmethoden verwenden.) 

Zum Schluss habe ich noch eine Methode hinzugefügt, redraw (). Wenn Sie also Ihre Schnittmethode/scaleType dynamisch im Code ändern möchten, können Sie die Ansicht zwingen, die Ansicht neu zu zeichnen. Zum Beispiel:

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

Um zu Ihrem benutzerdefinierten Top-Center-Third-Crop zurückzukehren, rufen Sie Folgendes auf:

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

Hier ist die Klasse:

/* 
 * 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

Gehen Sie vielleicht in den Quellcode für die Bildansicht auf Android und sehen Sie, wie der Bildausschnitt usw. gezeichnet wird, und kopieren Sie möglicherweise etwas Code in Ihre Methoden. Ich weiß nicht wirklich nach einer besseren Lösung, als dies zu tun. Ich habe Erfahrung mit der manuellen Größenänderung und dem Zuschneiden der Bitmap (Suche nach Bitmap-Transformationen), wodurch die tatsächliche Größe reduziert wird, jedoch immer noch ein gewisser Aufwand entsteht.

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

Wenn Sie Fresco (SimpleDraweeView) verwenden, können Sie dies ganz einfach mit:

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

Dies wäre für eine Top-Ernte.

Mehr dazu unter Referenzlink

0
fxhereng

Es gibt zwei Probleme mit den Lösungen hier:

  • Sie werden nicht im Android Studio-Layout-Editor gerendert (sodass Sie eine Vorschau auf verschiedene Bildschirmgrößen und Seitenverhältnisse anzeigen können).
  • Es skaliert nur nach der Breite. Abhängig von den Seitenverhältnissen von Gerät und Bild können Sie am Ende einen leeren Streifen erhalten

Diese kleine Änderung behebt das Problem (platzieren Sie den Code in onDraw und überprüfen Sie die Skalierungsfaktoren für Breite und Höhe):

@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