Skala ImageView TOP_CROP


88

Saya memiliki ImageViewyang menampilkan png yang memiliki rasio aspek lebih besar daripada perangkat (secara vertikal - artinya lebih panjang). Saya ingin menampilkan ini sambil mempertahankan rasio aspek, mencocokkan lebar induknya, dan menyematkan tampilan gambar ke bagian atas layar.

Masalah yang saya miliki dengan menggunakan CENTER_CROPsebagai tipe skala adalah bahwa itu akan (dapat dimengerti) memusatkan gambar yang diskalakan daripada menyelaraskan tepi atas ke tepi atas untuk tampilan gambar.

Masalahnya FIT_STARTadalah bahwa gambar akan sesuai dengan tinggi layar dan tidak memenuhi lebarnya.

Saya telah memecahkan masalah ini dengan menggunakan ImageView kustom dan menimpa onDraw(Canvas)serta menangani ini secara manual menggunakan kanvas; Masalah dengan pendekatan ini adalah 1) Saya khawatir mungkin ada solusi yang lebih sederhana, 2) Saya mendapatkan pengecualian mem VM saat memanggil super(AttributeSet)konstruktor saat mencoba menyetel src img 330kb saat heap memiliki 3 mb gratis (dengan ukuran tumpukan 6 mb) dan tidak tahu mengapa.

Setiap ide / saran / solusi sangat diterima :)

Terima kasih

ps i berpikir bahwa solusinya mungkin dengan menggunakan jenis skala matriks dan melakukannya sendiri, tetapi tampaknya bekerja sama atau lebih daripada solusi saya saat ini!


1
Apakah Anda mencoba dengan CENTER_CROP dan menyetel properti adjustViewBounds sebagai true dengan ImageView?
PravinCG

2
Ya saya sudah mencobanya, tidak berhasil, saya takut karena akan memperluas tampilan hingga lebar induknya, yang tidak akan lebih besar dari layar, dan kemudian menengahkan gambar di layar dengan tinggi berlebih / 2 menyembul dari atas dan bawah
Dori

Jawaban:


84

Oke, saya punya solusi yang berfungsi. Prompt dari Darko membuat saya melihat kembali kelas ImageView (terima kasih) dan telah menerapkan transformasi menggunakan Matrix (seperti yang awalnya saya duga tetapi tidak berhasil pada upaya pertama saya!). Di kelas imageView kustom saya, saya memanggil setScaleType(ScaleType.MATRIX)setelah super()di konstruktor, dan memiliki metode berikut.

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

Saya telah menempatkan int dalam setFrame()metode seperti dalam ImageView, panggilan ke configureBounds()dalam metode ini, di mana semua penskalaan dan matriks terjadi, jadi menurut saya logis (katakanlah jika Anda tidak setuju)

Di bawah ini adalah metode super.setFrame () dari 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;
    }

Temukan src kelas lengkap di sini


Terima kasih untuk kodenya, @doridori! Ini bekerja dengan baik! Saya hanya tidak mengerti mengapa Anda mengulangi metode "setFrame" dalam penjelasan Anda ... Saya hanya menggunakan yang pertama dengan sukses (dan sama sekali mengabaikan xD kedua)
Alesqui

3
Setelah bertarung dengan ini melalui tata letak xml selama dua jam, ini berhasil. Saya berharap saya bisa memberi Anda lebih banyak perahu.
Mark Beaton

5
Saya harus memanggil super () sebelum tubuh jika tidak, gambar tidak akan ditampilkan tanpa
pengecatan

1
@VitorHugoSchwaab Anda harus menggunakan thms seperti matrix.postTranslate (..)
Anton Kizema

1
tidak bisa menggunakan latar belakang? hanya gunakan src?
Egos Zhang

43

di sini adalah kode saya untuk memusatkannya di bagian bawah. Btw. dalam Dori's Code ada sedikit bug: Karena super.frame()dipanggil paling akhir, getWidth()metode ini mungkin mengembalikan nilai yang salah. Jika Anda ingin memusatkannya di atas cukup hapus baris postTranslate dan selesai. Hal yang menyenangkan adalah dengan kode ini Anda dapat memindahkannya ke mana pun Anda mau. (kanan, tengah => tidak masalah;)

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

    }

Bagus, terima kasih telah menunjukkan bugnya. Saya belum menyentuh kode ini untuk sementara waktu jadi ini mungkin saran yang bodoh tetapi untuk memperbaiki bug setWidth yang Anda tunjukkan, bisakah seseorang tidak hanya menggunakan (r-l)saja?
Dori

Tentunya garis tersebut if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))harus dibalik? Dengan kata lain, bukankah sebaiknya Anda menguji apakah gambar lebih besar dari bingkai? Saya sarankan untuk menggantinya denganif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P

Saya telah memilih lebar dari orang tua menggunakan ((View) getParent()).getWidth()sejak ImageViewhasMATCH_PARENT
sherpya

@Jay itu bekerja dengan baik. Saya membuat perhitungan matriks dalam metode onLayout (), yang dipanggil saat rotasi juga, di mana setFrame tidak.
kotak

Terima kasih untuk ini, berfungsi dengan sempurna, dan juga terima kasih telah memberikan cara untuk dengan cepat mengubah pangkas bawah menjadi pangkas teratas dengan mengomentari 1 baris kode
Moonbloom

39

Anda tidak perlu menulis Tampilan Gambar Kustom untuk mendapatkan TOP_CROPfungsionalitas tersebut. Anda hanya perlu memodifikasi matrixfile ImageView.

  1. Setel scaleTypeke matrixuntuk 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. Tetapkan matriks kustom untuk 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);
    

Melakukan ini akan memberi Anda TOP_CROPfungsionalitas.


2
Ini berhasil untuk saya. Namun, saya harus memeriksa scaleRation jika <1 Saya kemudian hanya mengubah scaleTypeke centerCropjika tidak, saya akan melihat ruang kosong di tepinya.
Arst

Bagaimana cara membuat ini berfungsi untuk kasus di mana ada kebutuhan untuk gambar rata bawah?
Sagar

26

Contoh ini bekerja dengan gambar yang dimuat setelah pembuatan objek + beberapa pengoptimalan. Saya menambahkan beberapa komentar dalam kode yang menjelaskan apa yang sedang terjadi.

Ingatlah untuk menelepon:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

atau

android:scaleType="matrix"

Sumber 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

}

Bagaimana cara membuat ini berfungsi untuk kasus di mana ada kebutuhan untuk gambar rata bawah?
Sagar

13

Berdasarkan Dori, saya menggunakan solusi yang mengubah skala gambar berdasarkan lebar atau tinggi gambar untuk selalu mengisi wadah di sekitarnya. Ini memungkinkan penskalaan gambar untuk mengisi seluruh ruang yang tersedia menggunakan titik kiri atas gambar, bukan di tengah sebagai titik asal (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);
}

Saya harap ini membantu - bekerja seperti suguhan dalam proyek saya.


11
Ini adalah solusi yang lebih baik ... Dan tambahkan: lebar pelampung = r - l; tinggi float = b - t;
Geltrude

9

Tak satu pun dari solusi ini berhasil untuk saya, karena saya menginginkan kelas yang mendukung pemangkasan sewenang-wenang baik dari arah horizontal maupun vertikal, dan saya ingin itu memungkinkan saya mengubah pemangkasan secara dinamis. Saya juga membutuhkan kompatibilitas Picasso , dan Picasso menyetel gambar yang dapat digambar dengan malas.

Implementasi saya diadaptasi langsung dari ImageView.java di AOSP. Untuk menggunakannya, nyatakan seperti itu dalam XML:

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

Dari sumber, jika Anda ingin memiliki hasil panen terbaik, hubungi:

imageView.setCropYCenterOffsetPct(0f);

Jika Anda ingin memiliki potongan bawah, hubungi:

imageView.setCropYCenterOffsetPct(1.0f);

Jika Anda ingin mendapatkan 1/3 hasil panen, panggil:

imageView.setCropYCenterOffsetPct(0.33f);

Selain itu, jika Anda memilih untuk menggunakan metode pemotongan lain, seperti fit_center, Anda dapat melakukannya dan tidak ada logika khusus ini yang akan dipicu. (Penerapan lain HANYA memungkinkan Anda menggunakan metode pemangkasannya).

Terakhir, saya menambahkan metode, redraw (), jadi jika Anda memilih untuk mengubah metode krop / scaleType secara dinamis dalam kode, Anda dapat memaksa tampilan untuk menggambar ulang. Sebagai contoh:

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

Untuk kembali ke pemangkasan sepertiga tengah atas kustom Anda, hubungi:

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

Ini kelasnya:

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

Mungkin masuk ke kode sumber untuk tampilan gambar di android dan lihat bagaimana itu menggambar tanaman pusat dll .. dan mungkin menyalin beberapa kode itu ke dalam metode Anda. saya tidak tahu untuk solusi yang lebih baik daripada melakukan ini. saya memiliki pengalaman secara manual mengubah ukuran dan memotong bitmap (mencari transformasi bitmap) yang mengurangi ukuran sebenarnya tetapi masih menciptakan sedikit overhead dalam prosesnya.


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: Anda dapat melakukan matriks apa pun di sini.
tianxia

onLayoutmenyelamatkan saya banyak! Terima kasih! Saya mengalami masalah di mana ia menghitung matriks tetapi tidak segera menampilkan gambar dan menambahkan onLayoutkode memecahkan masalah saya.
natsumiyu

1

Jika Anda menggunakan Fresco (SimpleDraweeView), Anda dapat dengan mudah melakukannya dengan:

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

Yang ini akan menjadi hasil panen terbaik.

Info lebih lanjut di Reference Link


0

Ada 2 masalah dengan solusinya di sini:

  • Mereka tidak dirender di editor tata letak Android Studio (sehingga Anda dapat melihat pratinjau di berbagai ukuran layar dan rasio aspek)
  • Ini hanya menskalakan menurut lebarnya, jadi tergantung pada rasio aspek perangkat dan gambar, Anda dapat berakhir dengan strip kosong di bagian bawah

Modifikasi kecil ini memperbaiki masalah (tempatkan kode di onDraw, dan periksa faktor skala lebar dan tinggi):

@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

Solusi Paling Sederhana: Klip gambar

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

Ini tidak akan memperbesar gambar jika lebih kecil dari tampilan, itulah sebabnya solusi lain tidak sesederhana itu.
OldSchool4664
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.