6
votes

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.

enter image description here

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.

enter image description here

This is my XML WITH rotation in it.

enter image description here

EDIT:

How to fix the bounds of the Image so the Text is aligned just above the image? enter image description here

4

4 Answers

6
votes

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 :

enter image description here

rotation="0"

rotation="45"

rotation="90"

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()

1
votes

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".

1
votes

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" />
0
votes

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