18

I have an image which is smaller than the container I would like it to fit inside of. I would like the image to stretch, keeping it's aspect ratio, to it's largest possible size.

To illustrate this problem:

<ImageView  android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:src="@drawable/thumbnail"
            android:scaleType="fitXY"
            android:adjustViewBounds="true"/>

The ImageView above would be stretched to fill the width of the container. The @drawable it contained would also stretch along the x axis to fit the width of ImageView which is perfect. The problem however is that the dimension labelled wrap_content, in this case height, remains the same size as the @drawables initial height.

I have read the documentation regarding ScaleType here and can't find the answer there.

The following image describes the above code:

enter image description here enter image description here

  Current behaviour               Desired Behaviour

Edit

An ImageView given scaleType="fitCenter" will accurately expand/shrink the @drawable inside of it to grow as large as possible while retaining it's aspect ratio.

The ImageViews dimensions are defined before the @drawable is scaled in any way. The ImageView dimensions are not effected by scaling of it's contained @drawable.

Graeme
  • 25,714
  • 24
  • 124
  • 186
  • 2
    To me, this is a common problem, one that I've never found a clean solution for. Add in a background 9-patch for a frame, and it's even worse to try and fix. I'm pretty sure this is something you'll have to do programmatically, but it'll be nice if someone does have a solution for this. – Kevin Coppock Sep 20 '11 at 14:38
  • 2
    +1 for a fully described question – Moog Sep 21 '11 at 09:06

2 Answers2

7

XML

The only solution to this in XML is to use "match_parent" or a discrete maximum value instead of "wrap_content" where possible. This will ensure the ImageView is the correct size, which will then meaning adding scaleType="fitCenter" will ensure the @drawable will then scale correctly.

Programatically

It's ugly, but you can resize the ImageView after it's dimensions have been given discrete values:

    final ImageView thumbnailView = (ImageView) toReturn.findViewById(R.id.thumbnail);  

    ViewTreeObserver thumbnailViewVto = thumbnailView.getViewTreeObserver();
    thumbnailViewVto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        private boolean changed = false;
        @Override
        public void onGlobalLayout() {
            if(!changed) {
                Drawable image = getResources().getDrawable(R.drawable.thumbnail);
                float heighToWidthRatio = image.getIntrinsicWidth()/image.getIntrinsicHeight();
                int height = thumbnailView.getHeight();

                thumbnailView.setLayoutParams(
                        new LayoutParams(
                                (int) (height * heighToWidthRatio), height));
                changed = true;
            }
        }
    });

EDIT

    final ImageView thumbnailView = (ImageView) toReturn.findViewById(R.id.thumbnail);  

    thumbnailView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Remove the GlobalOnLayout Listener so it only fires once.
            thumbnailView.getViewTreeObserver().removeGlobalOnLayoutListener(this)

            // Find the images Height to Width ratio
            Drawable image = getResources().getDrawable(R.drawable.thumbnail);
            float heighToWidthRatio = image.getIntrinsicWidth()/image.getIntrinsicHeight();

            // Use this ratio to discover the ratio of the ImageView to allow it to perfectly contain the image.
            int height = thumbnailView.getHeight();
            thumbnailView.setLayoutParams(new LayoutParams(
                                (int) (height * heighToWidthRatio), height));
        }
    });
Graeme
  • 25,714
  • 24
  • 124
  • 186
  • What is boolean `changed` for? – fikr4n Nov 06 '13 at 09:27
  • It's so the onGlobalLayout listener doesn't fire repeatedly. Older and wiser, I would now just remove the `OnGlobalLayoutListener` from the `ViewTreeObserver` as the first action in the `onGlobalLayout()` method. (See my edit for details) – Graeme Nov 06 '13 at 14:20
1

Looks like you want fitCenter, which uses Matrix.ScaleToFit CENTER.

mbeckish
  • 10,485
  • 5
  • 30
  • 55
  • This keep's the aspect ratio but the image doesn't fill anymore. This would work if the height of the ImageView was correct which can be done programatically but I would like it to happen through XML. – Graeme Sep 20 '11 at 14:30
  • @Graeme - From the link: "Compute a scale that will maintain the original src aspect ratio, but will also ensure that src fits entirely inside dst. *At least one axis (X or Y) will fit exactly.* The result is centered inside dst." - Their description seems to imply that the image will be stretched to fill either the width or height. Maybe it only shrinks to fit, but doesn't stretch to fit? – mbeckish Sep 20 '11 at 15:06
  • That description is exactly correct - since one of the dimensions is set to "wrap_content" however the size initialisation of the ImageView seems to happen before the scaling, therefore the height of View is already set and it is using the smallest axis to fit to, without enlarging it. – Graeme Sep 20 '11 at 15:15