16
votes

Background

There are various XML attributes for ImageView to scale its content , and various layout views that allow to place views and set their sizes.

However, I can't figure out how to scale an imageView nicely on some cases.

An example of it is to put the ImageView on the bottom (of a frameLayout, for example) , scale its width as much as possible, and still keep the aspect ratio (without cropping).

The problem

None of the combinations I've found worked, including various ScaleType values, adjustViewBounds, gravity ,...

It seems as if ImageView misses some important ScaleType values.

What I've tried

So far, the only solution that worked for me, is to set the imageView to the bottom (using the gravity set to bottom and center-horizontal), and use code, similar to this:

final ImageView logoBackgroundImageView = ...;
final LayoutParams layoutParams = logoBackgroundImageView.getLayoutParams();
final Options bitmapOptions = ImageService.getBitmapOptions(getResources(), R.drawable.splash_logo);
final int screenWidth = getResources().getDisplayMetrics().widthPixels;
layoutParams.height = bitmapOptions.outHeight * screenWidth / bitmapOptions.outWidth;
logoBackgroundImageView.setLayoutParams(layoutParams);

This usually works , and doesn't need much changes to make it work for when it's not in full screen, but I wonder if there is a simpler way.

It might not work in case the height gets to be too large, so you might want to change it so that if that's happening, you set the width to be smaller to allow everything fit the container.

The question

Is there a solution or a library that fixes the various issues related to the ImageView ?


EDIT: here's a sample image of what I'm trying to do, this time, within a vertical LinearLayout that bounds the ImageView in the middle, between 2 other views:

enter image description here

As you can see, on some (or all?) of the previews, the middle image is aligned to the right instead of staying on the middle (horizontal).

I want it to take all the space it got and scale (keeping aspect ratio), and yet be aligned to the bottom of the space that it has.

here's a sample XML of one combination I've tried :

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#FF339AE2">

    <View
        android:layout_width="match_parent"
        android:id="@+id/topView"
        android:layout_alignParentTop="true"
        android:layout_height="100dp"
        android:background="#FF00ff00"
        />


    <FrameLayout
        android:layout_below="@+id/topView"
        android:layout_width="match_parent"
        android:layout_centerHorizontal="true"
        android:layout_above="@+id/bottomView"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="match_parent"
            android:background="#FFffff00"
            android:layout_gravity="bottom|center_horizontal"
            android:src="@android:drawable/sym_def_app_icon"
            android:scaleType="fitEnd"
            android:layout_height="match_parent"
            />
    </FrameLayout>

    <View
        android:background="#FFff0000"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/bottomView"
        android:layout_width="match_parent"
        android:layout_height="100dp"/>

</RelativeLayout>

Again, note that I've tried multiple combinations, but none of them worked, so if you suggest something to change in the XML, please try it out first. Also note that the above is a POC (to make it easy to read).

BTW, the workaround I've suggested works on some (or most?) cases, but not all. You can notice it by just rotating your device. Maybe there is a way to fix it, or extend from ImageView to provide the extra case.

6
What about ImageView.ScaleType.FIT_END ?pskink
@pskink i've tried it. it will scale the image to the most-right end of itself. I've tried : android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:adjustViewBounds="true" android:scaleType="fitEnd"android developer
@pskink However if i set the height to "match_parent", I'm not sure what I'm seeing. Is it what you've meant?android developer
then post an image what you you gotpskink
so try this pastebin.com/CAkuJa9Ypskink

6 Answers

32
votes

You need a combination of three attributes:

android:layout_height="wrap_content"
android:scaleType="fitCenter"
android:adjustViewBounds="true"

The adjustViewBounds attribute is needed because ImageView measures itself to match the original dimensions of the drawable by default, without regard to any scaling performed according to the scale type.

8
votes

You can use is custom FitWidthAtBottomImageView to achieve this code:

public class FitWidthAtBottomImageView extends ImageView {
    public FitWidthAtBottomImageView(Context context) {
        super(context);
    }

    public FitWidthAtBottomImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public FitWidthAtBottomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public FitWidthAtBottomImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int i = getWidth() - getPaddingLeft() - getPaddingRight();
        int j = getHeight() - getPaddingTop() - getPaddingBottom();
        if (getBackground() != null) {
            getBackground().draw(canvas);
        }
        if (getDrawable() != null && getDrawable() instanceof BitmapDrawable) {
            Bitmap bitmap = ((BitmapDrawable) getDrawable()).getBitmap();
            int h = bitmap.getHeight() * i / bitmap.getWidth();
            canvas.drawBitmap(bitmap, null, new RectF(getPaddingLeft(), getPaddingTop() + j - h, getPaddingLeft() + i, getHeight() - getPaddingBottom()), null);
        } else {
            super.onDraw(canvas);
        }

    }
}

by manually draw the bottom aligned image that you want.

4
votes

this is a proof-om-concept custom ImageView, you will need to add missing constructors and perform the Matrix setting whenever ImageView's Drawable changes:

class V extends ImageView {
    public V(Context context) {
        super(context);
        setScaleType(ScaleType.MATRIX);
    }
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        Drawable d = getDrawable();
        if (d != null) {
            Matrix matrix = new Matrix();
            RectF src = new RectF(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
            RectF dst = new RectF(0, 0, w, h);
            matrix.setRectToRect(src, dst, Matrix.ScaleToFit.CENTER);
            float[] points = {
                    0, d.getIntrinsicHeight()
            };
            matrix.mapPoints(points);
            matrix.postTranslate(0, h - points[1]);
            setImageMatrix(matrix);
        }
    }
}

how it works in short:

  • first the Matrix is set by calling Matrix.setRectToRect() with stf == Matrix.ScaleToFit.CENTER, the result is the same as ImageView.ScaleType.FIT_CENTER

  • then in order to align the Drawable to the bottom Matrix.mapPoints() is called, it maps the Drawable's left/bottom corner and the result is transformed point as would be seen in the ImageView

  • and finally Matrix.postTranslate() translates the MAtrix in Y axis to the ImageView's bottom

0
votes

Try using android:scaleType="fitCenter"

Edit

Hm, I saw what you mean, another option is to have it fitEnd on the portrait xml and create an identical xml on layout-land folder (create it if needed) with android:scaleType="fitCenter" which will be used on landscape only

-1
votes

I used android:scaleType="fitXY" but it got me trying it so many times until the image did get fit the XY of the whole screen,,, what I mean there is a wierd bug in scaleType and fitEnd will work fine but you'll need to delete it and write again until it fit the end of any screen without corpping. Bottom line scaleType has bugs and bugs you until it gives you what you need.

-1
votes

Fist of all, I don't really understand what you really want for align image to the bottom from what I read your layout. My layout will make the image center, and scale you can modify the high of the bottom view to see how image will look like. Change the high of the top or bottom view the image size will scale up or down depend on the space available for imageView.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <View
        android:id="@+id/topView"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_alignParentTop="true"
        android:background="#FF00ff00" />


    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/bottomView"
        android:layout_below="@+id/topView">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center_horizontal"
            android:background="#FFffff00"
            android:scaleType="fitCenter"
            android:src="@android:drawable/sym_def_app_icon" />
    </FrameLayout>

    <View
        android:id="@+id/bottomView"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:background="#FFff0000" />
</RelativeLayout>

Hope this will fix your problem.