public
Last active

An ImageView with modified onMeasure which scales bounding box even if ImageView has layout width/height match_parent / fill_parent

  • Download Gist
ScalingImageView.java
Java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
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;
}
}

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.

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!

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.