Reputation: 80265
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.
Upvotes: 90
Views: 97382
Reputation: 859
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);
}
}
}
Upvotes: 0
Reputation: 894
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)
}
}
Upvotes: 1
Reputation: 611
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" />
Upvotes: 44
Reputation: 339
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" />
Upvotes: 8
Reputation: 1201
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>
Upvotes: 3
Reputation: 1261
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>
Upvotes: 114
Reputation: 80265
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.
Upvotes: 24
Reputation: 39836
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" />
Upvotes: 9