ImageView ölçeklendirme TOP_CROP


88

Bir sahip ImageView(- onun uzun anlamına dikey konuşma), cihazın daha büyük bir en-boy oranı bir PNG görüntülendiği olan. En boy oranını korurken, üst öğenin genişliğini eşleştirirken ve görüntü görünümünü ekranın üst kısmına sabitlerken bunu görüntülemek istiyorum.

CENTER_CROPÖlçek türü olarak kullanmakta yaşadığım sorun , üst kenarı görüntü görünümünün üst kenarı ile hizalamak yerine ölçeklenen görüntüyü (anlaşılabilir) ortalayacak olmasıdır.

Sorun FIT_START, görüntünün ekran yüksekliğine sığması ve genişliği doldurmamasıdır.

Bu sorunu özel bir ImageView onDraw(Canvas)kullanarak ve bunu tuvali kullanarak manuel olarak geçersiz kılarak ve işleyerek çözdüm; Bu yaklaşımla ilgili sorun şu: 1) Daha basit bir çözüm olabileceğinden endişeleniyorum, 2) super(AttributeSet)Yığın 3 mb boş olduğunda 330kb'lik bir src img ayarlamaya çalışırken kurucuyu ararken bir VM mem istisnası alıyorum (6 mb yığın boyutunda) ve nedenini bulamıyorum.

Herhangi bir fikir / öneri / çözüm çok açığız :)

Teşekkürler

ps Bir çözümün bir matris ölçeği türü kullanmak ve bunu kendim yapmak olabileceğini düşündüm, ancak bu benim mevcut çözümümle aynı veya daha fazla iş gibi görünüyor!


1
CENTER_CROP ile denediniz ve AdjustViewBounds özelliğini ImageView ile true olarak ayarladınız mı?
PravinCG

2
Evet, bunu denedim, korkmuyorum çünkü ekranı, ekrandan daha büyük olmayacak şekilde ebeveynini genişletene kadar genişletecek ve ardından görüntüyü ekranda ortalayarak fazla yükseklik / 2 yukarıdan çıkacak şekilde ortalayacaktır. ve alt
Dori

Yanıtlar:


85

Tamam, çalışan bir çözümüm var. Darko'dan gelen bilgi istemi ImageView sınıfına tekrar bakmamı sağladı (teşekkürler) ve dönüşümü bir Matrix kullanarak uyguladım (ilk başta şüphelendiğim ancak ilk denememde başarılı olamadım!). Özel imageView sınıfımda yapıcıda setScaleType(ScaleType.MATRIX)sonra çağırıyorum super()ve aşağıdaki yönteme sahibim .

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

setFrame()ImageView'da olduğu gibi yönteme int yerleştirdim, bu yönteme çağrı configureBounds(), tüm ölçeklendirme ve matris şeylerinin gerçekleştiği yer, bu yüzden bana mantıklı geliyor (katılmıyorsanız diyelim)

Aşağıda AOSP'nin super.setFrame () yöntemi verilmiştir.

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

Tüm sınıf src'yi burada bulun


Kod için teşekkürler, @doridori! Tamam çalıştı! Açıklamanızda "setFrame" yöntemini neden tekrarladınız anlamıyorum ... Sadece ilkini başarıyla kullandım (ve ikinci
xD'yi

3
Bununla iki saat boyunca xml düzeni aracılığıyla mücadele ettikten sonra, bu işe yaradı. Keşke sana daha fazla tekne verebilseydim.
Mark Beaton

5
Vücudun önünde super () çağırmak zorunda kaldım, aksi takdirde görüntü yeniden
boyanmadan

1
@VitorHugoSchwaab matrix.postTranslate (..)
Anton Kizema

1
arka planı kullanamıyor musunuz? yalnızca src mi kullanıyorsunuz?
Egos Zhang

43

işte altta ortalamak için kodum. Btw. Dori'nin Kodunda küçük bir hata var: super.frame()En sonunda çağrıldığı için, getWidth()yöntem yanlış değeri döndürebilir. Üstte ortalamak istiyorsanız, postTranslate satırını kaldırmanız yeterlidir ve bitirdiniz. Güzel olan şey, bu kodla istediğiniz yere taşıyabilmenizdir. (sağ, orta => sorun yok;)

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

    }

Harika, hatayı işaret ettiğiniz için teşekkürler. Bu koda bir süredir dokunmadım, bu yüzden bu aptalca bir öneri olabilir, ancak dikkat ettiğiniz setWidth hatasını düzeltmek için (r-l)bunun yerine kullanılamaz mı?
Dori

Şüphesiz çizgi if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))tersine çevrilmelidir? Başka bir deyişle, görüntünün çerçeveden daha büyük olup olmadığını test etmeniz gerekmez mi? Onu değiştirmenizi öneririmif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P

Kullandığım ebeveynden genişliğini aldı ettik ((View) getParent()).getWidth()beri ImageViewvarMATCH_PARENT
sherpya

@Jay harika çalışıyor. SetFrame'in olmadığı yerlerde döndürme sırasında da çağrılan onLayout () yönteminde matris hesaplamaları yapıyorum.
kutu

Bunun için teşekkürler, mükemmel çalışıyor ve ayrıca 1 satır kod
Moonbloom

39

TOP_CROPİşlevselliği elde etmek için Özel Resim Görünümü yazmanıza gerek yoktur . Sadece değiştirmeniz gerekir matrixarasında ImageView.

  1. Set scaleTypeiçin matrixiçin 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. Aşağıdakiler için özel bir matris ayarlayın 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);
    

Bunu yapmak size TOP_CROPişlevsellik kazandıracaktır.


2
Bu benim için çalıştı. Ancak, bu ise scaleRation kontrol etmek zorunda <1 Sonra sadece değiştirmek scaleTypeiçin centerCropaksi takdirde kenarlarda boşluk göreceksiniz.
Arst

Alttan hizalanmış görüntülere ihtiyaç duyulan durumlarda bu nasıl çalışır?
Sagar

26

Bu örnek, nesne oluşturulduktan sonra + bazı optimizasyonlarla yüklenen görüntülerle çalışır. Neler olduğunu açıklayan koda bazı yorumlar ekledim.

Aramayı unutmayın:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

veya

android:scaleType="matrix"

Java kaynağı:

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

}

Alttan hizalanmış görüntülere ihtiyaç duyulan durumlarda bu nasıl çalışır?
Sagar

13

Dori'ye dayanarak, çevreleyen kabı her zaman doldurmak için görüntünün genişliğine veya yüksekliğine göre görüntüyü ölçeklendiren bir çözüm kullanıyorum. Bu, bir görüntünün, başlangıç noktası olarak merkez yerine görüntünün sol üst noktasını kullanarak tüm kullanılabilir alanı doldurmasını sağlar (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);
}

Umarım bu yardımcı olur - projemde bir zevk gibi çalışır.


11
Bu daha iyi bir çözüm ... Ve şunu ekleyin: float width = r - l; şamandıra yüksekliği = b - t;
Geltrude

9

Bu çözümlerin hiçbiri benim için işe yaramadı, çünkü yatay veya dikey yönde rastgele bir mahsulü destekleyen bir sınıf istedim ve mahsulü dinamik olarak değiştirmeme izin vermesini istedim. Ayrıca Picasso uyumluluğuna da ihtiyacım vardı ve Picasso, resim çekmecelerini tembel bir şekilde ayarladı.

Benim uygulamam doğrudan AOSP'deki ImageView.java'dan uyarlandı . Kullanmak için XML'de böyle beyan edin:

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

En iyi mahsulün olmasını istiyorsanız kaynaktan arayın:

imageView.setCropYCenterOffsetPct(0f);

Alt kısımda bir ürün istiyorsanız, arayın:

imageView.setCropYCenterOffsetPct(1.0f);

Yolun 1 / 3'ünde bir mahsul almak istiyorsanız, arayın:

imageView.setCropYCenterOffsetPct(0.33f);

Ayrıca, fit_center gibi başka bir kırpma yöntemi kullanmayı seçerseniz, bunu yapabilirsiniz ve bu özel mantığın hiçbiri tetiklenmeyecektir. (Diğer uygulamalar YALNIZCA kırpma yöntemlerini kullanmanıza izin verir).

Son olarak, bir yöntem (redraw ()) ekledim, böylece kırpma yönteminizi / scaleType'ı kodda dinamik olarak değiştirmeyi seçerseniz, görünümü yeniden çizmeye zorlayabilirsiniz. Örneğin:

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

Özel üst-orta-üçüncü ekininize geri dönmek için şu numarayı arayın:

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

İşte sınıf:

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

Belki android üzerinde resim görünümü için kaynak koduna gidin ve orta kesimi vb. Nasıl çizdiğini görün ve belki bu kodun bir kısmını yöntemlerinize kopyalayın. Bunu yapmaktan daha iyi bir çözüm olduğunu gerçekten bilmiyorum. Bitmap'i manuel olarak yeniden boyutlandırma ve kırpma (bitmap dönüşümlerini arama) deneyimim var, bu da gerçek boyutunu azaltıyor, ancak yine de işlemde biraz ek yük yaratıyor.


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

}


computMatrix: burada herhangi bir matrisi yapabilirsiniz.
tianxia

onLayoutbeni çok kurtarıyor! Teşekkürler! Matrisi hesapladığı ancak görüntüyü hemen görüntülemediği bir sorunla karşılaştım onLayoutve kod eklemek sorunumu çözdü.
natsumiyu

1

Fresco (SimpleDraweeView) kullanıyorsanız , aşağıdakilerle kolayca yapabilirsiniz:

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

Bu, en iyi mahsul için olacaktır.

Referans Bağlantısında daha fazla bilgi


0

Buradaki çözümlerle ilgili 2 sorun var:

  • Android Studio düzen düzenleyicisinde görüntülenmezler (böylece çeşitli ekran boyutları ve en boy oranlarında önizleme yapabilirsiniz)
  • Yalnızca genişliğe göre ölçeklenir, bu nedenle cihazın ve görüntünün en boy oranlarına bağlı olarak altta boş bir şeritle sonuçlanabilirsiniz.

Bu küçük değişiklik sorunu giderir (kodu onDraw'a yerleştirin ve genişlik ve yükseklik ölçek faktörlerini kontrol edin):

@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

En Basit Çözüm: Resmi kırpın

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

Görüntüden daha küçükse bu, görüntüyü ölçeklendirmez, bu nedenle diğer çözümler o kadar basit değildir.
OldSchool4664
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.