Android: How to stretch an image to the screen width while maintaining aspect ratio?
I accomplished this with a custom view. Set layout_width="fill_parent" and layout_height="wrap_content", and point it to the appropriate drawable:
public class Banner extends View {
private final Drawable logo;
public Banner(Context context) {
super(context);
logo = context.getResources().getDrawable(R.drawable.banner);
setBackgroundDrawable(logo);
}
public Banner(Context context, AttributeSet attrs) {
super(context, attrs);
logo = context.getResources().getDrawable(R.drawable.banner);
setBackgroundDrawable(logo);
}
public Banner(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
logo = context.getResources().getDrawable(R.drawable.banner);
setBackgroundDrawable(logo);
}
@Override protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = width * logo.getIntrinsicHeight() / logo.getIntrinsicWidth();
setMeasuredDimension(width, height);
}
}
In the end, I generated the dimensions manually, which works great:
DisplayMetrics dm = new DisplayMetrics();
context.getWindowManager().getDefaultDisplay().getMetrics(dm);
int width = dm.widthPixels;
int height = width * mainImage.getHeight() / mainImage.getWidth(); //mainImage is the Bitmap I'm drawing
addView(mainImageView,new LinearLayout.LayoutParams(
width, height));
I just read the source code for ImageView
and it is basically impossible without using the subclassing solutions in this thread. In ImageView.onMeasure
we get to these lines:
// Get the max possible width given our constraints
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
// Get the max possible height given our constraints
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
Where h
and w
are the dimensions of the image, and p*
is the padding.
And then:
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
...
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
/* Parent says we can be as big as we want. Just don't be larger
than max size imposed on ourselves.
*/
result = Math.min(desiredSize, maxSize);
So if you have a layout_height="wrap_content"
it will set widthSize = w + pleft + pright
, or in other words, the maximum width is equal to the image width.
This means that unless you set an exact size, images are NEVER enlarged. I consider this to be a bug, but good luck getting Google to take notice or fix it. Edit: Eating my own words, I submitted a bug report and they say it has been fixed in a future release!
Another solution
Here is another subclassed workaround, but you should (in theory, I haven't really tested it much!) be able to use it anywhere you ImageView
. To use it set layout_width="match_parent"
, and layout_height="wrap_content"
. It is quite a lot more general than the accepted solution too. E.g. you can do fit-to-height as well as fit-to-width.
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
// This works around the issue described here: http://stackoverflow.com/a/12675430/265521
public class StretchyImageView extends ImageView
{
public StretchyImageView(Context context)
{
super(context);
}
public StretchyImageView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public StretchyImageView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
// Call super() so that resolveUri() is called.
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If there's no drawable we can just use the result from super.
if (getDrawable() == null)
return;
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int w = getDrawable().getIntrinsicWidth();
int h = getDrawable().getIntrinsicHeight();
if (w <= 0)
w = 1;
if (h <= 0)
h = 1;
// Desired aspect ratio of the view's contents (not including padding)
float desiredAspect = (float) w / (float) h;
// We are allowed to change the view's width
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
// We are allowed to change the view's height
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
int pleft = getPaddingLeft();
int pright = getPaddingRight();
int ptop = getPaddingTop();
int pbottom = getPaddingBottom();
// Get the sizes that ImageView decided on.
int widthSize = getMeasuredWidth();
int heightSize = getMeasuredHeight();
if (resizeWidth && !resizeHeight)
{
// Resize the width to the height, maintaining aspect ratio.
int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright;
setMeasuredDimension(newWidth, heightSize);
}
else if (resizeHeight && !resizeWidth)
{
int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
setMeasuredDimension(widthSize, newHeight);
}
}
}