Масштабування ImageView TOP_CROP


88

У мене ImageViewє файл, який відображає PNG, який має більший формат, ніж формат пристрою (вертикально кажучи - це означає, що він довший). Я хочу відобразити це, зберігаючи пропорції, відповідаючи ширині батьківського елемента та закріпивши перегляд зображення вгорі екрана.

Проблема, яку я маю при використанні CENTER_CROPв якості типу масштабу, полягає в тому, що воно (зрозуміло) відцентрує масштабоване зображення, замість того, щоб вирівнювати верхній край до верхнього краю подання зображення.

Проблема в FIT_STARTтому, що зображення буде відповідати висоті екрана, а не заповнювати ширину.

Я вирішив цю проблему за допомогою користувацького ImageView та відмінивши та обробивши onDraw(Canvas)це вручну за допомогою полотна; проблема з цим підходом полягає в тому, що 1) я переживаю, що може бути простіше рішення, 2) я отримую виняток VM mem при виклику super(AttributeSet)конструктора при спробі встановити src img 330 кб, коли купа має 3 мб вільного (з розміром купи 6 мб) і не можу зрозуміти, чому.

Будь-які ідеї / пропозиції / рішення дуже вітаються :)

Дякую

ps Я думав, що рішенням може бути використання типу матричного масштабу і зробити це самостійно, але це, здається, така ж чи більша робота, ніж моє поточне рішення!


1
Ви пробували з CENTER_CROP і встановлювали властивість AdjuViewBounds як істинне за допомогою ImageView?
PravinCG

2
Так, я спробував це, не маючи успіху, боюся, він розширить вигляд, поки не розширить батьківський екран, який не буде більшим за екран, а потім відцентрує зображення на екрані з надмірною висотою / 2, що тикає зверху і знизу
Дорі

Відповіді:


84

Гаразд, у мене є робоче рішення. Підказка від Дарко змусила мене ще раз поглянути на клас ImageView (спасибі) і застосувати перетворення за допомогою матриці (як я спочатку підозрював, але не мав успіху з першої спроби!). У своєму користувацькому класі imageView я викликаю setScaleType(ScaleType.MATRIX)після super()в конструкторі і маю такий метод.

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

Я розмістив int у setFrame()методі, як у ImageView, виклик до якого configureBounds()знаходиться всередині цього методу, де і відбувається все масштабування та матриця, тому мені здається логічним (скажімо, якщо ви не згодні)

Нижче наведено метод super.setFrame () від 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;
    }

Знайдіть повний клас src тут


Дякуємо за код, @doridori! Це працювало нормально! Я просто не розумію, чому ви пояснили метод "setFrame" у своєму поясненні ... Я з успіхом використав лише перший (і повністю проігнорував другий xD)
Alesqui,

3
Після боротьби з цим за допомогою макета xml протягом двох годин, це спрацювало. Я хотів би дати вам більше підводних човнів.
Mark Beaton,

5
Мені довелося зателефонувати супер () до тіла, інакше зображення не відображалося б без перефарбовування
sherpya

1
@VitorHugoSchwaab ти повинен використовувати thms як matrix.postTranslate (..)
Антон Кізема

1
не можу використовувати фон? використовувати лише src?
Egos Zhang

43

ось мій код для центрування його внизу. До речі. у Коді Дорі є невелика помилка: оскільки super.frame()виклик відбувається в самому кінці, getWidth()метод може повернути неправильне значення. Якщо ви хочете відцентрувати його вгорі, просто видаліть рядок postTranslate і все готово. Приємно те, що за допомогою цього коду ви можете перемістити його куди завгодно. (праворуч, по центру => немає проблем;)

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

    }

Чудово, дякую, що вказали на помилку. Я деякий час не торкався цього коду, тому це може бути дурною пропозицією, але щоб виправити помилку setWidth, на яку ви вказуєте, можна було б не просто використовувати її (r-l)?
Дорі

Напевно, лінію if((frameWidth > originalImageWidth) || (frameHeight > originalImageHeight))слід змінити? Іншими словами, чи не слід вам перевіряти, чи більше зображення, ніж кадр? Я пропоную замінити його наif((originalImageWidth > frameWidth ) || (originalImageHeight > frameHeight ))
Carlos P

Я вибрав ширину з батьків, використовуючи, ((View) getParent()).getWidth()оскільки ImageViewhasMATCH_PARENT
sherpya

@Jay це чудово працює. Я роблю матричні обчислення в методі onLayout (), який також викликається при обертанні, де setFrame цього не робить.
коробка

Дякуємо за це, працює чудово, а також дякуємо за те, що даєте можливість швидко змінити нижній урожай на верхній, коментуючи 1 рядок коду
Moonbloom

39

Вам не потрібно писати власний перегляд зображень, щоб отримати TOP_CROPфункціональність. Вам просто потрібно модифікувати matrixз ImageView.

  1. Встановіть scaleTypeдля matrixдля 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. Встановіть власну матрицю для 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);
    

Це дасть вам TOP_CROPфункціонал.


2
Це спрацювало для мене. Однак я повинен перевірити масштаб масштабу, якщо він <1, тоді просто змініть значення scaleTypeна, centerCropінакше я побачу порожній простір по краях.
Arst

Як зробити так, щоб це працювало у випадках, коли потрібні зображення, вирівняні по низу?
Сагар,

26

Цей приклад працює із зображеннями, які завантажуються після створення об'єкта + певна оптимізація. Я додав кілька коментарів у код, які пояснюють, що відбувається.

Не забудьте зателефонувати:

imageView.setScaleType(ImageView.ScaleType.MATRIX);

або

android:scaleType="matrix"

Джерело 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

}

Як зробити так, щоб це працювало у випадках, коли потрібні зображення, вирівняні по низу?
Сагар

13

На основі Дорі я використовую рішення, яке або масштабує зображення на основі ширини або висоти зображення, щоб завжди заповнювати навколишній контейнер. Це дозволяє масштабувати зображення, щоб заповнити весь доступний простір, використовуючи верхню ліву точку зображення, а не центр як початок (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);
}

Сподіваюся, це допоможе - це працює як ласощі в моєму проекті.


11
Це найкраще рішення ... І додайте: float width = r - l; висота поплавка = b - t;
Гельтруда

9

Жодне з цих рішень не працювало для мене, оскільки я хотів, щоб клас підтримував довільне обрізання як з горизонтального, так і з вертикального напрямку, і я хотів, щоб це дозволило мені динамічно змінювати обрізання. Мені також була потрібна сумісність Пікассо , і Пікассо ліниво встановлює малюнки, що малюють.

Моя реалізація адаптована безпосередньо з ImageView.java в AOSP . Щоб використовувати його, оголосіть так у XML:

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

З джерела, якщо ви хочете отримати найкращий урожай, телефонуйте:

imageView.setCropYCenterOffsetPct(0f);

Якщо ви хочете отримати нижній урожай, телефонуйте:

imageView.setCropYCenterOffsetPct(1.0f);

Якщо ви хочете отримати урожай на 1/3 шляху вниз, зателефонуйте:

imageView.setCropYCenterOffsetPct(0.33f);

Крім того, якщо ви вирішите використовувати інший метод обрізання, наприклад fit_center, ви можете це зробити, і жодна з цих нестандартних логік не спрацює. (ТІЛЬКИ інші реалізації дозволяють використовувати їх методи обрізання).

Нарешті, я додав метод redraw (), тому, якщо ви вирішите динамічно змінити метод обрізання / scaleType в коді, ви можете змусити перегляд перемалювати. Наприклад:

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

Щоб повернутися до власного обрізання вгорі по центру, зателефонуйте:

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

Ось клас:

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

Можливо, перейдіть у вихідний код для перегляду зображень на android і подивіться, як він малює центральне обрізання тощо., А можливо скопіюйте частину цього коду у свої методи. я насправді не знаю кращого рішення, ніж це робити. Я маю досвід ручного зміни розміру та обрізання растрового зображення (пошук растрових перетворень), що зменшує його фактичний розмір, але все одно створює невеликі накладні витрати в процесі.


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: тут ви можете зробити будь-яку матрицю.
tianxia

onLayoutрятує мене багато! Дякую! Я зіткнувся з проблемою, коли він обчислює матрицю, але не відображає зображення відразу, і додавання onLayoutкоду вирішує мою проблему.
natsumiyu

1

Якщо ви використовуєте Fresco (SimpleDraweeView), ви можете легко зробити це за допомогою:

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

Це було б для найкращого врожаю.

Більше інформації за посиланням


0

Тут є 2 проблеми з рішеннями:

  • Вони не відображаються в редакторі макета Android Studio (тому ви можете здійснювати попередній перегляд на різних розмірах екрана та пропорціях)
  • Він масштабується лише за шириною, тому, залежно від пропорцій пристрою та зображення, ви можете отримати порожню смужку внизу

Ця невелика модифікація вирішує проблему (розмістіть код у onDraw та перевірте коефіцієнти масштабу ширини та висоти):

@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

Найпростіше рішення: закріпіть зображення

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

Це не призведе до масштабування зображення, якщо воно менше, ніж вигляд, саме тому інші рішення не такі прості.
OldSchool4664
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.