17

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.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    What about ImageView.ScaleType.FIT_END ? – pskink Dec 02 '13 at 10:01
  • @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 Dec 02 '13 at 10:32
  • @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 Dec 02 '13 at 10:34
  • then post an image what you you got – pskink Dec 02 '13 at 10:41
  • @pskink OK, I will show a sample, including code. – android developer Jan 29 '15 at 13:04
  • Set android:background="#a00" for testing purposes to see image view's bounds – pskink Jan 29 '15 at 13:34
  • @pskink OK, I will now publish a new screenshot and code of it. – android developer Jan 29 '15 at 13:57
  • i see the problem now: fitEnd works only if width < height (if the image is a square shape) – pskink Jan 29 '15 at 18:48
  • you would need some custom matrix scale like this: http://pastebin.com/vJkqDfrH – pskink Jan 29 '15 at 18:50
  • @pskink Why don't you put it in a normal answer? Also, have you tried it? Please also explain how it works, and maybe make it nicer to use. – android developer Jan 29 '15 at 21:35
  • I never post untested code, first check it out as I'm not sure if that scale type is what you really want since your problem description is not completely clear, and yes it's just a proof of concept, you will need to polish it – pskink Jan 29 '15 at 21:39
  • What I want is that the image itself would scale as much as possible, keep its aspect ratio, be centered horizontally, but also have its bottom set on the bottom of the view. Using Fit-End seemed to have it all, except it's not horizontally centered. Maybe it has other issues that I didn't notice. – android developer Jan 29 '15 at 22:44
  • Did you run my snipped? – pskink Jan 29 '15 at 22:48
  • @pskink I've now tested it. It almost worked. on Nexus 9 and Nexus 10, only the bottom part of the image is shown (upper part is truncated). actually, even on other previews, I see it gets truncated, but not much is truncated. – android developer Jan 29 '15 at 23:10
  • Of course since you want the width fully scaled so if the scaled height is larger that view's height it will be cut – pskink Jan 29 '15 at 23:14
  • @pskink I meant without truncating anything. All content should be shown as the image tries to use both width and height. It's just aligned to the bottom and is centered-horizontally. I want it to scale as much as possible, but not get truncated and also keep aspect ratio. Having empty space is ok, but removing content isn't. The entire image should be visible. – android developer Jan 29 '15 at 23:16
  • 1
    so try this http://pastebin.com/CAkuJa9Y – pskink Jan 29 '15 at 23:35
  • @pskink This looks perfect. I could be wrong, but I think the image is a bit to the right. Maybe it's an optical illusion or just how the icon I use looks like. Can you please post it as an answer, and try to explain how it works and such? maybe I would want to create derivatives of this nice code that have other rules, so it's important to understand... – android developer Jan 30 '15 at 12:35
  • @pskink Odd. I've now tried what "corsair992" wrote, and it worked. Thing is that I was sure I've tried it already... Sorry for this. Take a +1 for trying to help me. Still would be nice to know how you did this code. Could be interesting. – android developer Jan 30 '15 at 12:42
  • hmm this what i get http://picpaste.com/layout-2015-01-30-145729-uiAmRkX8.png, imho this is not what is intended – pskink Jan 30 '15 at 14:01
  • @pskink This is ok, since the image is square. it kept its aspect ratio, scaled up as much as it can (according to the space that it has), stayed on the bottom part of what's available to it. You can set a background for the FrameLayout if you wish. I'm sorry if I wasn't clear. It is a bit weird that the left side looks to have more empty space than the right side. Maybe it's like on your solution, an illusion (or just how the image looks like). – android developer Jan 30 '15 at 14:03

6 Answers6

33

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.

corsair992
  • 3,050
  • 22
  • 33
8

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.

Johnny
  • 1,824
  • 23
  • 16
5

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

pskink
  • 23,874
  • 6
  • 66
  • 77
  • Thank you for that. I already got an answer that works fine (of "corsair992"), which say I can use "wrap_content" for height , "adjustViewBounds" set to true, and "scaleType" set to "fitCenter". However, I will give you +1 for all the effort. – android developer Jan 30 '15 at 13:38
  • 1
    how come it will adjust the image to the ImageView's bottom? it's impossible... try for example only ImageView with no view above and below: the image will be centered – pskink Jan 30 '15 at 13:47
  • Use it inside the sample I've written. I made it inside a FrameLayout which puts it at the bottom (using gravity), and because the ImageView will take only the size it needs, it will be at the bottom. Now that I've read what he wrote, maybe there are end cases that what he offered won't work? – android developer Jan 30 '15 at 13:49
0

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

Stefan
  • 5,203
  • 8
  • 27
  • 51
Evripidis Drakos
  • 872
  • 1
  • 7
  • 15
  • Doesn't work well. Try it too. For one of the preivew screens (Nexus 7 for example), it looks to be in the center of the available space. – android developer Jan 29 '15 at 13:59
  • No, I want it to scale the same way for all screens. below the ImageView there shouldn't be any empty space – android developer Jan 29 '15 at 14:02
  • unless you use an extremly wide image, the fitCenter on the landscape will cause the image to stretch to full height so it wont have any empty space above/below – Evripidis Drakos Jan 29 '15 at 16:03
  • The image can be of different sizes (and of course, apps should work well on all screens), so this can't be the solution. Sorry. – android developer Jan 29 '15 at 17:01
  • 2
    @androiddeveloper: You need to combine the `fitCenter` scale type with a height of `wrap_content`, and `adjustViewBounds` set to `true`. – corsair992 Jan 29 '15 at 20:24
  • @corsair992 "fitCenter" is already keeping the aspect-ratio : http://developer.android.com/reference/android/graphics/Matrix.ScaleToFit.html#CENTER – android developer Jan 29 '15 at 21:35
  • @androiddeveloper: You need to set the `adjustViewBounds` attribute to `true` in order to get the `ImageView` to wrap properly to the scaled displayed drawable dimensions, instead of the original dimensions. – corsair992 Jan 30 '15 at 06:10
  • @corsair992 Again, "fitCenter" is already keeping the aspect ratio, so "adjustViewBounds" won't do anything in this case. – android developer Jan 30 '15 at 12:26
  • @androiddeveloper: Have you tried out what I suggested? – corsair992 Jan 30 '15 at 12:32
  • Odd. Not only it works (and I'm sure I've tried it before), but I remember that "fitCenter" doesn't need it, as it's already keeping the aspect ratio... I will now tick your answer. Thank you. – android developer Jan 30 '15 at 12:44
  • @androiddeveloper: Setting the scale type to `fitCenter` does preserve the drawable aspect ratio, but as I mentioned in the previous comment, the default wrapping measurement of `ImageView` is to match the original unscaled dimensions of the drawable, so you still need the `adjustViewBounds` attribute to ensure proper measurement. You might have tried it before without setting the height to `wrap_content`, in which case it would have no effect. Also, this answer was not posted by me, and although it's basically correct, the poster might want to edit the additional information into his answer. – corsair992 Jan 30 '15 at 13:07
  • @corsair992 Oops. Please post the answer so that I could tick it. – android developer Jan 30 '15 at 13:16
  • @androiddeveloper: OK, I have posted an answer. Also, I answered a question of yours some time ago, can you confirm that it solved the issue: http://stackoverflow.com/questions/20932297 :) – corsair992 Jan 30 '15 at 13:39
-1

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.

3lomahmed
  • 849
  • 6
  • 10
  • "fitXY" is used when you don't care about aspect-ratio. about "fitEnd", it acts in a weird way so i'm not sure if that's the best thing. Is there maybe an alternative to the imageView ? maybe a third party library? or maybe using a matrix for the scaleType ? – android developer Dec 02 '13 at 12:30
  • fitEnd is a proper way of doing what you want, what kind of problems do you have with it? – pskink Dec 02 '13 at 13:57
  • @pskink Just try it. I've tested it and it always didn't work as it should for all previews. – android developer Jan 29 '15 at 12:51
-1

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.