6

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

Dennis Anderson
  • 1,348
  • 2
  • 14
  • 33

4 Answers4

6

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

bwt
  • 17,292
  • 1
  • 42
  • 60
  • looks good, let me try this tomorrow ! will keep you posted ! – Dennis Anderson Apr 18 '17 at 20:00
  • Can you tell what formula is that? – azizbekian Apr 18 '17 at 20:59
  • the width (resp height) after rotation depends on both the width and height before rotation, e.g. when rotation = 0 : sin = 0, cos = 1 so width after = width before (obviously), when rotation = 90 : sin = 1, cos = 0 so width after = HEIGHT before. I'll try to add a more complete explanation tomorrow – bwt Apr 18 '17 at 21:30
  • 3
    @azizbekian This was such a clever answer that I sat down and figured out what is going on. Here is a link to my calculations for what it is worth. http://i.imgur.com/zZUFpul.jpg – Cheticamp Apr 19 '17 at 01:34
  • Works like a charm ! very nice calculations ! just a minor question, can you also make it work with minus degrees like -90? then my feature is complete. Could just check if the value is < 0 and then do + 360, that would be the easy fix, but maybe your method can handle this in a more clever way? – Dennis Anderson Apr 19 '17 at 07:19
  • @Cheticamp, thanks! Look nice! One question I have. I kinda forgot trigonometry after school, how do you clarify that those angles are actually theta? – azizbekian Apr 19 '17 at 07:43
  • @DennisAnderson it does no work with -90 ? The result should be the same as 270 – bwt Apr 19 '17 at 08:51
  • @Cheticamp thanks for the picture, it's better than what I intended to do, so I have added it to the answer – bwt Apr 19 '17 at 08:53
  • @bwt , yes it seems to ignore it. I also have the usecase where when I do view.animate().rotate(90).start(); and its not doing the onMeasure(), what will be your advise? I shall award you with the bounty because this calculation is the answer for the original question ! Triple thanks for that ! – Dennis Anderson Apr 19 '17 at 10:32
  • overriding `setRotation()` and calling `requestLayout()` would trigger `onMeasure()`. It is usefull if you want to set the rotation programatically, but not for an animation (doing layout a pass for every frame is horribly slow). Being able to animate the rotation would be very nice, I'll try to find a solution – bwt Apr 19 '17 at 10:46
  • @azizbekian Thetas are numbered 1-4 clockwise from left. Vertical dashed lines are parallel. The 1st & 2nd thetas are equal because they are alternate angles (transversal of a parallel lines). The 4th theta is also equal since its triangle is congruent with the 2nd theta's triangle. The 3rd shares an angle with the 4th when added = 90 degree. What I really did to convince myself was to think of the vertical lines as chains dangling and imagining how they would change through a rotation - closed at zero degree rotation and all 90 at 90 degree rotation. – Cheticamp Apr 19 '17 at 10:54
  • 1
    @DennisAnderson I finally managed to make a version usable with `animate()`. It is quite hackish, so I added it as another answer and left this one as is. I am not sure wich one is the best – bwt Apr 20 '17 at 10:55
  • btw small follow-up: adjustViewBounds doesn't do the required function it normally does. The Bounds of the imageView stays at the Height of how the image was after Rotation - is still fixable? – Dennis Anderson Apr 26 '17 at 15:08
  • good point. The bound adjustement is done on the drawable's size before rotation. I'm note sure if there is a simple solution. I'll try to find something – bwt Apr 26 '17 at 16:48
  • if you want I can open a new question with bounty on this topic ;) googling everything but can't seem te find the answer. – Dennis Anderson Apr 28 '17 at 06:20
  • I don't have much time right now, but I will look into it this weekend. There is no need for another bounty, at least not for me. I think it is an interesting question and I would really like to get to the bottom of it – bwt Apr 28 '17 at 07:23
  • There is a much simplier solution (without support of animation): https://stackoverflow.com/questions/21355784/android-rotate-whole-layout/21475913#21475913 – OneWorld Nov 27 '19 at 13:33
1

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

Community
  • 1
  • 1
Feuby
  • 708
  • 4
  • 7
1

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" />
bwt
  • 17,292
  • 1
  • 42
  • 60
  • Dude ! I love the effort you took into this to answer my questions ! I'm going to try this one out very soon ! :D thanks man ! will let you know if it worked ! – Dennis Anderson Apr 21 '17 at 14:53
0

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

xorgate
  • 2,244
  • 1
  • 24
  • 36
  • If it refers to view, how it happens, that the `View` is not rotated when looking with "Show layout bounds" mode on? – azizbekian Apr 18 '17 at 18:18