Search code examples
androidviewrotationimageviewbounds

Image out of bounds after transformation on view


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


Solution

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