Skip to content

Instantly share code, notes, and snippets.

@tprochazka
Last active July 15, 2022 12:16
Show Gist options
  • Save tprochazka/5486822 to your computer and use it in GitHub Desktop.
Save tprochazka/5486822 to your computer and use it in GitHub Desktop.
/*
* Copyright (C) 2013 Tomáš Procházka
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.inmite.android.fw.view;
import java.lang.reflect.Field;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import eu.inmite.android.fw.DebugLog;
/**
* Special version of ImageView which allow enlarge width of image if android:adjustViewBounds is true.
*
* <p>It simulate HTML behaviour &lt;img src="" widh="100" /&gt;</p>
* <p><a href="http://stackoverflow.com/questions/6202000/imageview-one-dimension-to-fit-free-space-and-second-evaluate-to-keep-aspect-rati">Stackoverflow question link</a></p>
*
* <p>It also allow set related view which will be used as reference for size measure.</p>
*
* @author Tomáš Procházka &lt;<a href="mailto:tomas.prochazka@inmite.eu">tomas.prochazka@gmail.com</a>&gt;
* @version $Revision: 0$ ($Date: 6.6.2011 18:16:52$)
*/
public class ImageView extends android.widget.ImageView {
private int mDrawableWidth;
private int mDrawableHeight;
private boolean mAdjustViewBoundsL;
private int mMaxWidthL = Integer.MAX_VALUE;
private int mMaxHeightL = Integer.MAX_VALUE;
private View relatedView;
public ImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// hack for acces some private field of parent :-(
Field f;
try {
f = android.widget.ImageView.class.getDeclaredField("mAdjustViewBounds");
f.setAccessible(true);
setAdjustViewBounds((Boolean) f.get(this));
f = android.widget.ImageView.class.getDeclaredField("mMaxWidth");
f.setAccessible(true);
setMaxWidth((Integer) f.get(this));
f = android.widget.ImageView.class.getDeclaredField("mMaxHeight");
f.setAccessible(true);
setMaxHeight((Integer) f.get(this));
} catch (Exception e) {
DebugLog.e("Access to parent private field failed", e);
}
}
public ImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageView(Context context) {
super(context);
}
public void setAdjustViewBounds(boolean adjustViewBounds) {
super.setAdjustViewBounds(adjustViewBounds);
mAdjustViewBoundsL = adjustViewBounds;
}
public void setMaxWidth(int maxWidth) {
super.setMaxWidth(maxWidth);
mMaxWidthL = maxWidth;
}
public void setMaxHeight(int maxHeight) {
super.setMaxHeight(maxHeight);
mMaxHeightL = maxHeight;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getDrawable() == null) {
setMeasuredDimension(0, 0);
return;
}
mDrawableWidth = getDrawable().getIntrinsicWidth();
mDrawableHeight = getDrawable().getIntrinsicHeight();
int w = 0;
int h = 0;
// 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;
if (mDrawableWidth > 0) {
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.
if (mAdjustViewBoundsL) {
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
desiredAspect = (float) w / (float) h;
}
}
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,
mMaxWidthL, widthMeasureSpec);
// Get the max possible height given our constraints
heightSize = resolveAdjustedSize(h + ptop + pbottom,
mMaxHeightL, 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) {
// Try adjusting width to be proportional to height
if (resizeWidth) {
int newWidth = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright;
if (/*newWidth <= widthSize &&*/newWidth > 0) {
widthSize = Math.min(newWidth, mMaxWidthL);
heightSize = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
}
}
// Try adjusting height to be proportional to width
if (resizeHeight) {
int newHeight = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
if (/*newHeight <= heightSize && */newHeight > 0) {
heightSize = Math.min(newHeight, mMaxHeightL);
widthSize = (int) (desiredAspect * (heightSize - ptop - pbottom)) + pleft + pright;
}
}
}
}
} 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 = resolveSize(w, widthMeasureSpec);
heightSize = resolveSize(h, heightMeasureSpec);
}
//Log.d(Constants.LOGTAG, mDrawableWidth + ":" + mDrawableHeight + " to " + widthSize + ":" + heightSize);
setMeasuredDimension(widthSize, heightSize);
if (relatedView != null) {
//Log.i(Constants.LOGTAG, getTag() + " onMeasure:" + widthSize + ", " + heightSize + " update size of related view!");
relatedView.getLayoutParams().width = widthSize;
relatedView.getLayoutParams().height = heightSize;
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
//Log.d(Constants.LOGTAG, getTag() + " onLayout:" + left + ", " + top + ", " + right + ", " + bottom);
}
/**
* Experimental. This view will be set to the same size as this image.
*/
public void setRelatedView(View view) {
relatedView = view;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//Log.d(Constants.LOGTAG, getTag() + " onSizeChanged:" + w + ", " + h + ", " + oldw + ", " + oldh);
}
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;
}
}
@dalewking
Copy link

While your code works great for the case where you need to expand a smaller image to a bigger size, it doesn't work for the opposite case which ImageView supports of shrinking and maintaining aspect ratio. I was able to hack it to work for me using:

                    if (/*newWidth <= widthSize &&*/newWidth > 0) {
                        widthSize = Math.min(Math.min(newWidth, mMaxWidthL), widthSize);
                        heightSize = (int) ((widthSize - pleft - pright) / desiredAspect) + ptop + pbottom;
                    }

Which only works for me because my limiting factor is always width and not height.

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