I'm having a problem with displaying my image.
I have an Image I want to display full screen. So I have this Imageview with match_parent and 20dp padding.
It looks good but when I apply rotation on it, it seems that the bounds of the view doesn't change and the image can get clipped out of the screen ! Totally don't want that to happen! How do I rescale the image so that the image also fits in the ImageView when its 90 degrees rotated.
This is my XML WITH rotation in it.
EDIT:
How to fix the bounds of the Image so the Text is aligned just above the image?
The rotation is not taken into account when measuring the view and calculating the scale ratio. A possible solution is to do it yourself :
public class RotatedImageView extends ImageView {
...
constructors
...
private double mRotatedWidth;
private double mRotatedHeight;
private boolean update() {
Drawable d = getDrawable();
if (d == null) {
return false;
}
int drawableWidth = d.getIntrinsicWidth();
int drawableHeight = d.getIntrinsicHeight();
if (drawableWidth <= 0 || drawableHeight <= 0) {
return false;
}
double rotationRad = getRotation() / 180 * Math.PI;
// calculate intrinsic rotated size
// see diagram
mRotatedWidth = (Math.abs(Math.sin(rotationRad)) * drawableHeight
+ Math.abs(Math.cos(rotationRad)) * drawableWidth);
mRotatedHeight = (Math.abs(Math.cos(rotationRad)) * drawableHeight
+ Math.abs(Math.sin(rotationRad)) * drawableWidth);
return true;
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (update()) {
double ratio = mRotatedWidth / mRotatedHeight;
int wMax = Math.min(getDefaultSize(Integer.MAX_VALUE, widthMeasureSpec), getMaxWidth());
int hMax = Math.min(getDefaultSize(Integer.MAX_VALUE, heightMeasureSpec), getMaxHeight());
int w = (int) Math.min(wMax, hMax * ratio);
int h = (int) Math.min(hMax, wMax / ratio);
setMeasuredDimension(w, h);
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
private final float[] values = new float[9];
protected void onDraw(Canvas canvas) {
if (update()) {
int availableWidth = getMeasuredWidth();
int availableHeight = getMeasuredHeight();
float scale = (float) Math.min(availableWidth / mRotatedWidth, availableHeight / mRotatedHeight);
getImageMatrix().getValues(values);
setScaleX(scale / values[Matrix.MSCALE_X]);
setScaleY(scale / values[Matrix.MSCALE_Y]);
}
super.onDraw(canvas);
}
@Override
public void setRotation(float rotation) {
super.setRotation(rotation);
requestLayout();
}
}
adjustViewBounds
must be true :
<com.mypackage.RotatedImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:adjustViewBounds="true"
android:rotation="90"
android:maxWidth="100dp"
android:maxHeight="100dp"
android:scaleType="fitCenter"
android:src="@drawable/test" />
A nice explanation of the calculation, courtesy of Cheticamp :
UPDATE: Now trying to adjust the bounds. There is no difference between wrap_content
and match_parent
(both grow as much as possible, based on the image aspect). You should instead use maxWidth
and / or maxHeight
, or put it in a LinearLayout
with a 0 size and a weight.
It is also not animatable, adjusting bounds while animating requires a layout pass for each frame, which is very inefficient. See the other answer for a version usable with View.animate()
Another version of the RotatedImageView which rotation can be animated with a ViewPropertyAnimator
. The idea is the same, but the scaling is done in onDraw()
instead of onMeasure()
, so it does not need a layout pass each time.
In order to make the animation work, I had to hijack the update listener. If you want to use your own listener, don't forget to invalidate()
the view in onAnimationUpdate()
.
public class RotatedImageView2 extends ImageView {
...
constructors
...
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int beforeWidth = getMeasuredWidth();
int beforeHeight = getMeasuredHeight();
int max = Math.max(beforeWidth, beforeHeight);
// try to grow
setMeasuredDimension(getDefaultSize(max, widthMeasureSpec), getDefaultSize(max, heightMeasureSpec));
}
private final float[] values = new float[9];
@Override
protected void onDraw(Canvas canvas) {
Drawable d = getDrawable();
if (d == null) {
return;
}
int drawableWidth = d.getIntrinsicWidth();
int drawableHeight = d.getIntrinsicHeight();
if (drawableWidth <= 0 || drawableHeight <= 0) {
return;
}
double rotationRad = getRotation() / 180 * Math.PI;
double rotatedWidth = (Math.abs(Math.sin(rotationRad)) * drawableHeight
+ Math.abs(Math.cos(rotationRad)) * drawableWidth);
double rotatedHeight = (Math.abs(Math.cos(rotationRad)) * drawableHeight
+ Math.abs(Math.sin(rotationRad)) * drawableWidth);
int availableWidth = getMeasuredWidth();
int availableHeight = getMeasuredHeight();
float scale = (float) Math.min(availableWidth / rotatedWidth, availableHeight / rotatedHeight);
getImageMatrix().getValues(values);
setScaleX(scale / values[Matrix.MSCALE_X]);
setScaleY(scale / values[Matrix.MSCALE_Y]);
super.onDraw(canvas);
}
@Override
public void setRotation(float rotation) {
super.setRotation(rotation);
// force redraw
invalidate();
}
@Override
public ViewPropertyAnimator animate() {
// force redraw on each frame
// (a ViewPropertyAnimator does not use setRotation())
return super.animate().setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
invalidate();
}
});
}
}
Use example :
<com.mypackage.RotatedImageView2
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:adjustViewBounds="true"
android:rotation="90"
android:scaleType="fitCenter"
android:src="@drawable/test" />
according to a research leading to this topic i wonder if @Sarge Borsch answer could work in your case.
Try setting
android:scaleType="centerInside"
android:adjustViewBounds="true"
If centerInside is not correct because you want display in center, maybe try to position the imageview instead of the image inside.
Another suggestion: your imageview is set on "wrap_content" and i don't know exactly the order of everything but maybe the problem comes because it rotates after calculating dimensions (because of wrap_content). I think it is a possibility because the screenshoot you put shows that the image is not even fitting the width. TL;DR : try to fix the imageview size (padding on activity + match_parent) instead of wrap content, in combination of "adjustViewBounds".
The attribute android:rotation
refers to the View that is the ImageView, not its contents.
If you want to rotate the contents, either set a new BItmap as content, or override onDraw()
and rotate the canvas