87

Often asked, never answered (at least not in a reproducible way).

I have an image view with an image that is smaller than the view. I want to scale the image to the width of the screen and adjust the height of the ImageView to reflect the proportionally correct height of the image.

<ImageView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
/>

This results in the image centered at its original size (smaller then the screen width) with margins at the side. No good.

So I added

android:adjustViewBounds="true" 

Same effect, no good. I added

android:scaleType="centerInside"

Same effect, no good. I changed centerInside to fitCenter. Same effect, no good. I changed centerInside to centerCrop.

android:scaleType="centerCrop"

Now, finally, the image is scaled to the width of the screen - but cropped at top and bottom! So I changed centerCrop to fitXY.

android:scaleType="fitXY"

Now the image is scaled to the width of the screen but not scaled on the y-axis, resulting in a distorted image.

Removing android:adjustViewBounds="true" has no effect. Adding an android:layout_gravity, as suggested elsewhere, has again no effect.

I have tried other combinations -- to no avail. So, please does anyone know:

How do you set up the XML of an ImageView to fill the width of the screen, scale a smaller image to fill the entire view, displaying the image with its aspect ratio without distortion or cropping?

EDIT: I also tried setting an arbitrary numeric height. This only has an effect with the centerCrop setting. It will distort the image vertically according to the view height.

Charles
  • 50,943
  • 13
  • 104
  • 142
Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Have you tried `android:scaleType="fitCenter"`? – Michael Celey Dec 21 '12 at 15:20
  • 1
    @BrainSlugs83 I have and it does work for me, still. Also, the question was posed to determine what the asker had tried. There's no need for the snarkiness, especially not seven months after it was posted. – Michael Celey Aug 01 '13 at 03:02
  • It does not work. -- See below; Also, I have tried it and can verify it does not work (if you have tried it and see different results, please discuss below and show us what we are doing wrong -- the only conclusion I can come to is that you have misunderstood the question, or have not tried it). – BrainSlugs83 Aug 02 '13 at 19:54

8 Answers8

114

I have solved this by creating a java-class that you include in your layout-file:

public class DynamicImageView extends ImageView {

    public DynamicImageView(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        final Drawable d = this.getDrawable();

        if (d != null) {
            // ceil not round - avoid thin vertical gaps along the left/right edges
        final int width = MeasureSpec.getSize(widthMeasureSpec);
        final int height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            this.setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

Now, you use this by added your class to your layout-file:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <my.package.name.DynamicImageView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:scaleType="centerCrop"
        android:src="@drawable/about_image" />
</RelativeLayout>
Mark Martinsson
  • 1,241
  • 2
  • 9
  • 5
  • 4
    +1 Great solution, works perfectly also for Nexus 7. And the best part is, that is also works correctly within the Layout Editor in Eclipse. – Ridcully Jul 12 '13 at 07:05
  • Works on Galaxy S4. Why shouldn't it :) THx – Malachiasz Sep 30 '13 at 10:47
  • Thanks. Great solution. Easy enough modify to do the same based on the heightMeasureSpec. – speedynomads Nov 07 '13 at 09:43
  • 1
    Yes but not... This solution blur the images. If I use ImageView and fitCenter with fixed height the image is not blured (but fixed height isn't a solution for me). How to avoid bluring? – Geltrude Nov 12 '13 at 15:23
  • I was looking for this solution for weeks, kudos! I want to upscale one image though but this works perfect – Soko Oct 15 '14 at 05:44
  • Almost perfect. This solution doesn't take into account padding values. However, you can use margin to the same effect. – Prasad Silva Apr 05 '15 at 01:12
  • upscaling is done only from api18 and above as stated in the documentation, this workaround is awesome for earlier versions. http://developer.android.com/reference/android/widget/ImageView.html#setAdjustViewBounds%28boolean%29 – Gianluca P. Feb 24 '16 at 12:45
43

I had the same problem, as yours. After 30 minutes of trying diffirent variations I solved the problem in this way. It gives you the flexible height, and width adjusted to the parent width

<ImageView
            android:id="@+id/imageview_company_logo"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:adjustViewBounds="true"
            android:scaleType="fitCenter"
            android:src="@drawable/appicon" />
kigibek
  • 601
  • 6
  • 3
24

There is no viable solution within the XML layout standard.

The only reliable way to react to a dynamic image size is to use LayoutParams in code.

Disappointing.

Mundi
  • 79,884
  • 17
  • 117
  • 140
  • Please, change an accepted answer. It is wrong now. – CoolMind Oct 07 '21 at 09:58
  • Thanks @CoolMind, but I have not looked at this framework for years. Which answer would you prefer? – Mundi Nov 25 '21 at 16:50
  • Thank you for the reply! The solution of [kigibek](https://stackoverflow.com/a/28916681/2914140) helped in my particular problem. Maybe I am not right, you can choose any you like. – CoolMind Nov 25 '21 at 18:12
9
  android:scaleType="fitCenter"

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.

edit:

The problem here is that the layout_height="wrap_content" is not "allowing" the image to expand. You'll have to set a size for it, for that change

  android:layout_height="wrap_content"

to

  android:layout_height="100dp"  // or whatever size you want it to be

edit2:

works fine:

<ImageView
    android:id="@+id/imageView1"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:scaleType="fitCenter"
    android:src="@drawable/img715945m" />
Budius
  • 39,391
  • 16
  • 102
  • 144
  • 2
    This results in the first version of the image: centered, not expanded horizontally, with margins on both sides. No good. – Mundi Dec 21 '12 at 15:52
  • 2
    This does not work. It results in an image with the exact height, but not expanded horizontally. No good. Also see my edit in the question. – Mundi Dec 21 '12 at 16:00
  • could you post the exact image you're using? – Budius Dec 21 '12 at 16:03
  • 1
    http://www.nasa.gov/images/content/715945main__NOB0093_4x3_516-387.jpg The important part is that it seems to be smaller than the screen of the Nexus 7. – Mundi Dec 21 '12 at 16:09
  • my first edit was using `100dp` and because it's a bigger image it have to be a bigger value, check my new edit. Works fine here. – Budius Dec 21 '12 at 16:13
  • 2
    No good. I do not know the height - it is dynamic. It will result in unpredictable distortions. The edit in my question already covers this. Sorry - but thanks for your efforts! Shame on Google! – Mundi Dec 21 '12 at 16:18
  • It's simple: after you download (I'm assuming it comes from the net) the bitmap you'll have to `getHeight` and change your `ImageView.LayoutParams` to be that size. – Budius Dec 21 '12 at 16:20
  • 1
    I knew that this is an option, but I was looking for an **XML** solution. Still, you should edit this into your answer. Thanks! – Mundi Dec 21 '12 at 17:51
  • You shouldn't edit my answer because you don't agree with what I said. That's not how it works. Edit is to fix spelling, formatting, etc, not content. Regarding your actual question: XML are for static content, if the image is dynamically downloaded you need a dynamic handling. Furthermore, in case you're using those images on a ListView or other view that you want to keep performance, you should pre-scale the bitmap off the main thread, then apply to the ImageView without scale. – Budius Dec 23 '12 at 00:16
  • You are right about the editing, sorry for that. I know howto use `LayoutParams`, but this solution is not satifsactory. The XML should be able to accomodate this simple case. – Mundi Dec 24 '12 at 14:31
  • "Edit" is not for fixing formatting or spelling, etc. If you try to make too minor of an edit, SO will even tell you that is not what edit is for. (Which is quite annoying.) – BrainSlugs83 Jul 31 '13 at 20:41
8

This is a small addition to Mark Martinsson's excellent solution.

If your image's width is larger than its height, then Mark's solution will leave space at the top and bottom of the screen.

The below fixes this by first comparing the width and height: if the image width >= height, then it will scale the height to match the screen height, and then scale the width to preserve the aspect ratio. Similarly, if the image height > width, then it will scale the width to match the screen width and then scale the height to preserve the aspect ratio.

In other words, it properly satisfies the definition of scaleType="centerCrop" :

http://developer.android.com/reference/android/widget/ImageView.ScaleType.html

Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).

package com.mypackage;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;

public class FixedCenterCrop extends ImageView
{
    public FixedCenterCrop(final Context context, final AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec)
    {
        final Drawable d = this.getDrawable();

        if(d != null) {
            int height = MeasureSpec.getSize(heightMeasureSpec);
            int width = MeasureSpec.getSize(widthMeasureSpec);

            if(width >= height)
                height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            else
                width = (int) Math.ceil(height * (float) d.getIntrinsicWidth() / d.getIntrinsicHeight());

            this.setMeasuredDimension(width, height);

        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

This solution automatically works in either portrait or landscape mode. You reference it in your layout just as you do in Mark's solution. E.g.:

<com.mypackage.FixedCenterCrop
    android:id="@+id/imgLoginBackground"
    android:src="@drawable/mybackground"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:scaleType="centerCrop" />
3

I ran into the same problem, but with a fixed height, scaled width keeping the image's original aspect ratio. I solved it via a weighted linear layout. You can hopefully modify it for your needs.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/image"
        android:layout_width="0px"
        android:layout_height="180dip"
        android:layout_weight="1.0"
        android:adjustViewBounds="true"
        android:scaleType="fitStart" />

</LinearLayout>
tbm
  • 1,142
  • 12
  • 22
  • This solution worked great for me. It allowed me to give my ImageView a specific height and have the ImageView dynamically adjust it's width in order to maintain its aspect ratio. – Luke Mar 04 '16 at 15:23
1

Kotlin version of Mark Martinsson's answer:

class DynamicImageView(context: Context, attrs: AttributeSet) : AppCompatImageView(context, attrs) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val drawable = this.drawable

    if (drawable != null) {
        //Ceil not round - avoid thin vertical gaps along the left/right edges
        val width = View.MeasureSpec.getSize(widthMeasureSpec)
        val height = Math.ceil((width * drawable.intrinsicHeight.toFloat() / drawable.intrinsicWidth).toDouble()).toInt()
        this.setMeasuredDimension(width, height)
    } else {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}
ngu
  • 874
  • 1
  • 9
  • 23
0

One more addition to Mark Matinsson's solution. I found that some of my images were over scaled than others. So I modified the class so that the image is scaled by a maximum factor. If the image is too small to be scaled at max width without becoming blurry, it stops scaling beyond the max limit.

public class DynamicImageView extends ImageView {

    final int MAX_SCALE_FACTOR = 2;
    public DynamicImageView(final Context context) {
        super(context);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        final Drawable d = this.getDrawable();

        if (d != null) {
            // ceil not round - avoid thin vertical gaps along the left/right edges
            int width = View.MeasureSpec.getSize(widthMeasureSpec);
            if (width > (d.getIntrinsicWidth()*MAX_SCALE_FACTOR)) width = d.getIntrinsicWidth()*MAX_SCALE_FACTOR;
            final int height = (int) Math.ceil(width * (float) d.getIntrinsicHeight() / d.getIntrinsicWidth());
            this.setMeasuredDimension(width, height);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
azmath
  • 863
  • 1
  • 11
  • 30