How to align ImageView to the bottom, fill its width and height, and keep its aspect ratio?
Asked Answered
P

6

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.

Pedalfer answered 2/12, 2013 at 9:55 Comment(21)
What about ImageView.ScaleType.FIT_END ?Westminster
@Westminster 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"Pedalfer
@Westminster However if i set the height to "match_parent", I'm not sure what I'm seeing. Is it what you've meant?Pedalfer
then post an image what you you gotWestminster
@Westminster OK, I will show a sample, including code.Pedalfer
Set android:background="#a00" for testing purposes to see image view's boundsWestminster
@Westminster OK, I will now publish a new screenshot and code of it.Pedalfer
i see the problem now: fitEnd works only if width < height (if the image is a square shape)Westminster
you would need some custom matrix scale like this: pastebin.com/vJkqDfrHWestminster
@Westminster 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.Pedalfer
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 itWestminster
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.Pedalfer
Did you run my snipped?Westminster
@Westminster 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.Pedalfer
Of course since you want the width fully scaled so if the scaled height is larger that view's height it will be cutWestminster
@Westminster 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.Pedalfer
so try this pastebin.com/CAkuJa9YWestminster
@Westminster 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...Pedalfer
@Westminster 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.Pedalfer
hmm this what i get picpaste.com/layout-2015-01-30-145729-uiAmRkX8.png, imho this is not what is intendedWestminster
@Westminster 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).Pedalfer
A
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.

Arlana answered 30/1, 2015 at 13:38 Comment(9)
This will work on any size and aspect ratio of images, right? smaller and larger than the ImageView size, right?Pedalfer
@androiddeveloper: Yes, it should. Smaller images won't be upscaled though.Arlana
What do you mean they won't? but that's the whole purpose of what I asked for... to scale it as much as possible. Also, how come you say it won't work, while what I did on this sample is to use the small icon of apps? Please explain.Pedalfer
@androiddeveloper: Sorry, my mistake. Yes, it will be upscaled too.Arlana
Can you please check it out? I can't try it right now... Also try various aspect ratios.Pedalfer
@androiddeveloper: Tested and confirmed on all aspect ratios and scaling.Arlana
ok, I will take your word on this and tick this answer. Thank you.Pedalfer
@androiddeveloper: Also, was this old issue of yours solved: stackoverflow.com/questions/20932297 ? If so, then you can accept my answer there too :)Arlana
I don't remember this question and what I did with it. Sorry.Pedalfer
H
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.

Heisler answered 5/10, 2015 at 8:31 Comment(0)
W
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

Westminster answered 30/1, 2015 at 13:33 Comment(3)
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.Pedalfer
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 centeredWestminster
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?Pedalfer
R
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

Rickyrico answered 29/1, 2015 at 13:54 Comment(13)
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.Pedalfer
No, I want it to scale the same way for all screens. below the ImageView there shouldn't be any empty spacePedalfer
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/belowRickyrico
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.Pedalfer
@androiddeveloper: You need to combine the fitCenter scale type with a height of wrap_content, and adjustViewBounds set to true.Arlana
@Arlana "fitCenter" is already keeping the aspect-ratio : developer.android.com/reference/android/graphics/…Pedalfer
@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.Arlana
@Arlana Again, "fitCenter" is already keeping the aspect ratio, so "adjustViewBounds" won't do anything in this case.Pedalfer
@androiddeveloper: Have you tried out what I suggested?Arlana
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.Pedalfer
@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.Arlana
@Arlana Oops. Please post the answer so that I could tick it.Pedalfer
@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: stackoverflow.com/questions/20932297 :)Arlana
J
-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.

Jovian answered 2/12, 2013 at 11:53 Comment(3)
"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 ?Pedalfer
fitEnd is a proper way of doing what you want, what kind of problems do you have with it?Westminster
@Westminster Just try it. I've tested it and it always didn't work as it should for all previews.Pedalfer
S
-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.

Stricker answered 3/2, 2015 at 3:59 Comment(1)
Don't understand why it is not the centering the image in landscape mode? It center on my screen in landscape mode.Stricker

© 2022 - 2024 — McMap. All rights reserved.