Skip to content

Instantly share code, notes, and snippets.

@Nilzor
Last active May 17, 2018 11:22
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save Nilzor/5402789 to your computer and use it in GitHub Desktop.
Save Nilzor/5402789 to your computer and use it in GitHub Desktop.
An ImageView with modified onMeasure which scales bounding box even if ImageView has layout width/height match_parent / fill_parent
package nilzor.public.views;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ImageView;
public class ScalingImageView extends ImageView {
private boolean mAdjustViewBounds;
private int mMaxWidth;
private int mMaxHeight;
public ScalingImageView(Context context) {
super(context);
}
public ScalingImageView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
// getAdjustViewBounds() was added in api level 16, so for backwards compatibility sake...
public void setAdjustViewBounds(boolean adjustViewBounds) {
super.setAdjustViewBounds(adjustViewBounds);
mAdjustViewBounds = adjustViewBounds;
}
@Override
public void setMaxWidth(int maxWidth) {
super.setMaxWidth(maxWidth);
mMaxWidth = maxWidth;
}
@Override
public void setMaxHeight(int maxHeight) {
super.setMaxHeight(maxHeight);
mMaxHeight = maxHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable mDrawable = getDrawable();
int mDrawableWidth = mDrawable.getIntrinsicWidth();
int mDrawableHeight = mDrawable.getIntrinsicHeight();
//------------
int w;
int h;
// Desired aspect ratio of the view's contents (not including padding)
float desiredAspect = 0.0f;
// We are allowed to change the view's width
boolean resizeWidth = false;
// We are allowed to change the view's height
boolean resizeHeight = false;
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
final int actualWidth = MeasureSpec.getSize(widthMeasureSpec);
final int actualHeight = MeasureSpec.getSize(heightMeasureSpec);
if (mDrawable == null) {
// If no drawable, its intrinsic size is 0.
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
// We are supposed to adjust view bounds to match the aspect
// ratio of our drawable. See if that is possible.
desiredAspect = (float) w / (float) h;
if (mAdjustViewBounds) {
// Original Android code setting whether to resizeHeight / width.
/* resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; */
// Modified code which resizes no matter what MeasureSpec is set to. Works better with fill_parent/match_parent.
float actualAspect = (float) actualWidth/ (float) actualHeight;
if (actualAspect > desiredAspect) resizeWidth = true;
else if (actualAspect < desiredAspect) resizeHeight = true;
}
}
int pleft = getPaddingLeft();
int pright = getPaddingRight();
int ptop = getPaddingTop();
int pbottom = getPaddingBottom();
int widthSize;
int heightSize;
if (resizeWidth || resizeHeight) {
/* If we get here, it means we want to resize to match the
drawables aspect ratio, and we have the freedom to change at
least one dimension.
*/
// 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);
if (desiredAspect != 0.0f) {
// See what our actual aspect ratio is
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
boolean done = false;
// Try adjusting width to be proportional to height
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
}
}
// Try adjusting height to be proportional to width
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
/* We are either don't want to preserve the drawables aspect ratio,
or we are not allowed to change view dimensions. Just measure in
the normal way.
*/
w += pleft + pright;
h += ptop + pbottom;
w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());
widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}
Log.i("F&C", "Setting dimen " + widthSize + " x " + heightSize + " for " +getId() + " Adjusted w/h:" + resizeWidth + "/" + resizeHeight);
setMeasuredDimension(widthSize, heightSize);
}
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(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);
break;
case MeasureSpec.AT_MOST:
// Parent says we can be as big as we want, up to specSize.
// Don't be larger than specSize, and don't be larger than
// the max size imposed on ourselves.
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
break;
}
return result;
}
}
@Nilzor
Copy link
Author

Nilzor commented Apr 17, 2013

I have only changed two "business lines" of code from the original code of Android v17's onMeasure(). I had to do some hacks in order to get it compile though, as a lot of private variables and methods (resolveAdjustedSize) were used.

@kramer65
Copy link

I'm using Picasso (http://square.github.io/picasso/) to load an image from a URL. Because of this mDrawable.getIntrinsicWidth() returns a NullPointer. Would anybody have an idea how this could be solved (maybe by only measuring the IntrinsicWidth once the image has been received by Picasso)?

All tips are welcome!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment