Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 258
android developer Avatar asked Dec 02 '13 09:12

android developer


2 Answers

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.

like image 59
corsair992 Avatar answered Sep 19 '22 14:09

corsair992


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.

like image 41
Johnny Avatar answered Sep 20 '22 14:09

Johnny