ImageView to scale to fixed height, but crop excess width
Asked Answered
M

5

14

I would like my ImageView to scale in a particular fashion:

  • Scale so that the height of the image always fits the height of the ImageView
  • Crop any excess width

A picture speaks louder than a 1000 words, so here is a representation of how I want my ImageView to behave. Suppose it has a fixed height of say 100dp and suppose its width is match_parent.

enter image description here

Note that

  • on the phone layout, the image height is stretched, but the sides are cropped, akin to CROP_CENTER.
  • on the tablet layout, the image is also stretched to fit the ImageView height, behaving like FIT_CENTER

I suspect I need scaleType:matrix, but after that I'm lost. How can I make sure an image fits Y, but crops X?

Malinin answered 29/1, 2014 at 8:43 Comment(7)
could you provide a screen shot of what you are trying to achieve ?Sliver
Umm... Don't you understand my paint creation?Malinin
sorry but no i dont , please provide your source code as wellSliver
you paint creation was good enough, but this one is great!Equivocate
@Amrola you're trolling!Malinin
@Malinin no , but sorry .Sliver
In xml, use android:adjustViewBounds="true" .. thanks to https://mcmap.net/q/633301/-scaling-an-image-to-max-available-width-keeping-aspect-ratio-and-avoiding-imageview-extra-spaceShena
M
6

With a little help from my friends Carlos Robles and pskink, came up with the following custom ImageView:

public class FitYCropXImageView extends ImageView {
    boolean done = false;

    @SuppressWarnings("UnusedDeclaration")
    public FitYCropXImageView(Context context) {
        super(context);
        setScaleType(ScaleType.MATRIX);
    }

    @SuppressWarnings("UnusedDeclaration")
    public FitYCropXImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setScaleType(ScaleType.MATRIX);
    }

    @SuppressWarnings("UnusedDeclaration")
    public FitYCropXImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setScaleType(ScaleType.MATRIX);
    }

    private final RectF drawableRect = new RectF(0, 0, 0,0);
    private final RectF viewRect = new RectF(0, 0, 0,0);
    private final Matrix m = new Matrix();
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (done) {
            return;//Already fixed drawable scale
        }
        final Drawable d = getDrawable();
        if (d == null) {
            return;//No drawable to correct for
        }
        int viewHeight = getMeasuredHeight();
        int viewWidth = getMeasuredWidth();
        int drawableWidth = d.getIntrinsicWidth();
        int drawableHeight = d.getIntrinsicHeight();
        drawableRect.set(0, 0, drawableWidth, drawableHeight);//Represents the original image
        //Compute the left and right bounds for the scaled image
        float viewHalfWidth = viewWidth / 2;
        float scale = (float) viewHeight / (float) drawableHeight;
        float scaledWidth = drawableWidth * scale;
        float scaledHalfWidth = scaledWidth / 2;
        viewRect.set(viewHalfWidth - scaledHalfWidth, 0, viewHalfWidth + scaledHalfWidth, viewHeight);

        m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER /* This constant doesn't matter? */);
        setImageMatrix(m);

        done = true;

        requestLayout();
    }
}
Malinin answered 29/1, 2014 at 14:55 Comment(5)
i dont think that Matrix.ScaleToFit.CENTER will work, does it really work?Gamal
For me it does... I don't have a deep insight in the setRectToRect method, but if looks fine on my device! (As does STRETCH.)Malinin
indeed as two rects have the same aspect ratio any ScaleToFit will workGamal
great! it works also for a FitXCropYImageView, croping the height while filling with the width.Wizardry
Can you please tell me what changes I have to make to get FitXCropYImageView ?Chucho
S
28

In xml, use:

    android:scaleType="centerCrop"
    android:adjustViewBounds="true"

from & thanks to: https://mcmap.net/q/633301/-scaling-an-image-to-max-available-width-keeping-aspect-ratio-and-avoiding-imageview-extra-space

Shena answered 1/8, 2016 at 19:52 Comment(0)
M
6

With a little help from my friends Carlos Robles and pskink, came up with the following custom ImageView:

public class FitYCropXImageView extends ImageView {
    boolean done = false;

    @SuppressWarnings("UnusedDeclaration")
    public FitYCropXImageView(Context context) {
        super(context);
        setScaleType(ScaleType.MATRIX);
    }

    @SuppressWarnings("UnusedDeclaration")
    public FitYCropXImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setScaleType(ScaleType.MATRIX);
    }

    @SuppressWarnings("UnusedDeclaration")
    public FitYCropXImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setScaleType(ScaleType.MATRIX);
    }

    private final RectF drawableRect = new RectF(0, 0, 0,0);
    private final RectF viewRect = new RectF(0, 0, 0,0);
    private final Matrix m = new Matrix();
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (done) {
            return;//Already fixed drawable scale
        }
        final Drawable d = getDrawable();
        if (d == null) {
            return;//No drawable to correct for
        }
        int viewHeight = getMeasuredHeight();
        int viewWidth = getMeasuredWidth();
        int drawableWidth = d.getIntrinsicWidth();
        int drawableHeight = d.getIntrinsicHeight();
        drawableRect.set(0, 0, drawableWidth, drawableHeight);//Represents the original image
        //Compute the left and right bounds for the scaled image
        float viewHalfWidth = viewWidth / 2;
        float scale = (float) viewHeight / (float) drawableHeight;
        float scaledWidth = drawableWidth * scale;
        float scaledHalfWidth = scaledWidth / 2;
        viewRect.set(viewHalfWidth - scaledHalfWidth, 0, viewHalfWidth + scaledHalfWidth, viewHeight);

        m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER /* This constant doesn't matter? */);
        setImageMatrix(m);

        done = true;

        requestLayout();
    }
}
Malinin answered 29/1, 2014 at 14:55 Comment(5)
i dont think that Matrix.ScaleToFit.CENTER will work, does it really work?Gamal
For me it does... I don't have a deep insight in the setRectToRect method, but if looks fine on my device! (As does STRETCH.)Malinin
indeed as two rects have the same aspect ratio any ScaleToFit will workGamal
great! it works also for a FitXCropYImageView, croping the height while filling with the width.Wizardry
Can you please tell me what changes I have to make to get FitXCropYImageView ?Chucho
E
1

If you use scaleType:matrix you will need to create your own Matrix and asign it to the view by means of setImageMatrix(Matrix) or manually modify the matrix at hen onMEasure method of a customImageView.

public class MyImageView extends ImageView   {

 boolean done=false;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (done)
            return;

        final Drawable d = getDrawable();
        final int drawableW = d.getIntrinsicWidth();
        final int drawableH = d.getIntrinsicHeight();
        float ratio =  drawableW / drawableH;

        //int width = getMeasuredWidth();
        int height = getMeasuredHeight();

        float scale=height/drawableH;

          Matrix m = getImageMatrix();

          float[] f = new float[9];
          m.getValues(f);

          f[Matrix.MSCALE_X]=scale;
          f[Matrix.MSCALE_Y]=scale;

          m.setValues(f);  

        done = true;

        requestLayout();

    }

}
Equivocate answered 29/1, 2014 at 8:55 Comment(7)
yeah, thats why you adjust the height in the onmeasure method. aslo you change the width to keep the ratio, and since the scaletype is center_crop, if the widht is too much, it will be croppedEquivocate
if you give me a second i will explain how to do it.Equivocate
finally i took the time and write a solution working with the matrix. i think it will work well in any caseEquivocate
Wow... Didn't expect to get so low-level. Thanks!Malinin
But what if the drawable is not yet set at measure?Malinin
until they build the scaletype:fitX or scaletype:fitY, im afraid we have to go low level!Equivocate
you can check for that, after of if (done) and return if there isn't drawable. The system will keep calling onMeasure. Also you can migrate this code to your activity, and do all the maths after you assign the drawable. Of course the functions would be different, but the logic would be the same.Equivocate
G
1
    LinearLayout ll = new LinearLayout(this);
    ll.setOrientation(LinearLayout.VERTICAL);
    LayoutParams params;

    final ImageView iv0 = new ImageView(this);
    //iv0.setBackgroundColor(0xffff0000);
    params = new LayoutParams(LayoutParams.MATCH_PARENT, 100);
    ll.addView(iv0, params);

    final ImageView iv1 = new ImageView(this);
    //iv1.setBackgroundColor(0xff00ff00);
    params = new LayoutParams(60, 100);
    ll.addView(iv1, params);

    setContentView(ll);

    Runnable action = new Runnable() {
        @Override
        public void run() {
            Drawable d = getResources().getDrawable(R.drawable.layer0);
            int dw = d.getIntrinsicWidth();
            int dh = d.getIntrinsicHeight();
            RectF src = new RectF(0, 0, dw, dh);

            ImageView[] iviews = {iv0, iv1};
            for (int i = 0; i < iviews.length; i++) {
                ImageView iv = iviews[i];
                iv.setImageDrawable(d);
                iv.setScaleType(ScaleType.MATRIX);

                float h = iv.getHeight();
                float w = iv.getWidth();
                float cx = w / 2;
                float scale = h / dh;
                float deltaw = dw * scale / 2;
                RectF dst = new RectF(cx - deltaw, 0, cx + deltaw, h);
                Matrix m = new Matrix();
                m.setRectToRect(src, dst, ScaleToFit.FILL);
                iv.setImageMatrix(m);
            }
        }
    };
    iv1.post(action);
Gamal answered 29/1, 2014 at 9:59 Comment(4)
Some word of explanation, maybe? What if the drawable is not yet set at the end of the ImageView message queue?Malinin
what you mean by "if the drawable is not yet set" ?Gamal
You perform iv.setImageDrawable(d); in the Runnable. What if the drawable is fetched asynchronously from the internet and you have no guarantee about layout having happened when you get your hands on it, and conversely, having the drawable when layout occurs.Malinin
i use Runnable only because i need to make sure that ImageViews are already layout and have non zero width & height, you can assign your image Drawable any time you want provided that ImageView is non zero sized (you can also do it by extending ImageView and overriding onSizeChanged)Gamal
D
0

If you want to display the center of the image, use:

android:scaleType="centerCrop"
android:adjustViewBounds="true"

If you want to show the edge of the image instead of the center, use:

android:scaleType="matrix"
android:adjustViewBounds="true"
Debouchment answered 23/3, 2022 at 6:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.