Android: Scale a Drawable or background image?
Asked Answered
W

13

144

On a layout I want to scale the background image (keeping its aspect ratio) to the space allocated when the page gets created. Does anyone have an idea how to do this?

I am using layout.setBackgroundDrawable() and a BitmapDrawable() to set gravity for clipping and filling, but don't see any option for scaling.

Wake answered 9/9, 2009 at 16:46 Comment(1)
I had this problem and followed Dweebo's recommendation with the change suggested by Anke: changed fitXY to fitCenter. Worked well.Piccalilli
M
93

To customize background image scaling create a resource like this:

<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center"
    android:src="@drawable/list_bkgnd" />

Then it will be centered in the view if used as background. There are also other flags: http://developer.android.com/guide/topics/resources/drawable-resource.html

Masculine answered 20/2, 2012 at 13:46 Comment(8)
I tried using bitmap but the image is simply centered, not scaled as Sam is asking. When I use ImageView, it works beautifully, no muss/fuss. (n.b.: I'm also not scrolling in my case.) What would I augment bitmap with in order to scale the image?Garfield
Possible values are: "top" | "bottom" | "left" | "right" | "center_vertical" | "fill_vertical" | "center_horizontal" | "fill_horizontal" | "center" | "fill" | "clip_vertical" | "clip_horizontal"Masculine
none of these parameters scales bitmap, by keeping it's aspect ration, so the answer is wrong.Hellen
Dear Malachiasz, 'center' does respect the aspect ratio. But it will not scale the image down. Which means that it is a somewhat danger feature to use. Depending on the resolution of the target you never know how scaled the thing will come out exactly. Another half-assed solution by Google.Boiling
have an way to add scaleType attribute?Momentum
Google really wants us to use this for high performances launchscreen when the logo cannot even scale?Noshow
Only way I've found to do this is to make the bitmap a lot bigger when it's being used for the launch/splash screen...don't use the ic_launcher...Iormina
This solution seems dated now and the limitations should be pointed out. It doesn't seem to work if the background source is a vector image, as in newer Android. "Bitmap" here really does mean bitmap.Redfin
L
58

Haven't tried to do exactly what you want, but you can scale an ImageView using android:scaleType="fitXY"
and it will be sized to fit into whatever size you give the ImageView.

So you could create a FrameLayout for your layout, put the ImageView inside it, and then whatever other content you need in the FrameLayout as well w/ a transparent background.

<FrameLayout  
android:layout_width="fill_parent" android:layout_height="fill_parent">

  <ImageView 
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:src="@drawable/back" android:scaleType="fitXY" />

  <LinearLayout>your views</LinearLayout>
</FrameLayout>
Lishalishe answered 11/9, 2009 at 20:24 Comment(7)
Is it possible to do this on a Surface. If not, Can I show the preview(for a recorder app) using FrameLayout?Flame
@dweebo: Have you tried this? It doesn't seem to be working for me.Aquacade
Downvoted since using two UI components instead of one is a bad idea especially for such busy components as ListView. Most likely you will get troubles while scrolling. Proper solution: https://mcmap.net/q/158823/-android-scale-a-drawable-or-background-imageMasculine
I tried a scaleType of fitCenter and centerCrop (along with a few variations of the drawable/image for different densities) and it worked! Either scale type made sense for me - more of a personal preference based on the image, I suspect. Note that I used merge instead of FrameLayout, and there's no scrolling in my particular case.Garfield
centerInside is proper, not fitXY, as fitXY doesn't keep aspect ratio of the imageHellen
Don't use FrameLayout for more than one child, use RelativeLayout insteadPhasia
Works fine for me, even within ScrollViews. Don't understand all the complaints as this is a simple solution that doesn't require multiple files (and all the resultant maintenance issues).Stancil
S
41

There is an easy way to do this from the drawable:

your_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:drawable="@color/bg_color"/>
    <item>
        <bitmap
            android:gravity="center|bottom|clip_vertical"
            android:src="@drawable/your_image" />
    </item>

</layer-list>

The only downside is that if there is not enough space, your image won't be fully shown, but it will be clipped, I couldn't find an way to do this directly from a drawable. But from the tests I did it works pretty well, and it doesn't clip that much of the image. You could play more with the gravity options.

Another way will be to just create an layout, where you will use an ImageView and set the scaleType to fitCenter.

Schofield answered 21/2, 2014 at 9:4 Comment(2)
This solution is definitely what I was looking for a long time to replace the background property of my theme and avoid image scale differently when view has Tabs or not. Thanks for sharing this.Anthesis
It shows error: error: <item> must have a 'name' attribute.Donnetta
D
23

Use image as background sized to layout:

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

    <ImageView
        android:id="@+id/imgPlaylistItemBg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:adjustViewBounds="true"
        android:maxHeight="0dp"
        android:scaleType="fitXY"
        android:src="@drawable/img_dsh" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >


    </LinearLayout>

</FrameLayout>
Declivitous answered 24/9, 2014 at 12:56 Comment(1)
Thanks! Also android:scaleType="centerCrop" works.Davidson
B
18

To keep the aspect ratio you have to use android:scaleType=fitCenter or fitStart etc. Using fitXY will not keep the original aspect ratio of the image!

Note this works only for images with a src attribute, not for the background image.

Blackpoll answered 23/12, 2010 at 13:18 Comment(0)
H
5

When you set the Drawable of an ImageView by using the setBackgroundDrawable method, the image will always be scaled. Parameters as adjustViewBounds or different ScaleTypes will just be ignored. The only solution to keep the aspect ratio I found, is to resize the ImageView after loading your drawable. Here is the code snippet I used:

// bmp is your Bitmap object
int imgHeight = bmp.getHeight();
int imgWidth = bmp.getWidth();
int containerHeight = imageView.getHeight();
int containerWidth = imageView.getWidth();
boolean ch2cw = containerHeight > containerWidth;
float h2w = (float) imgHeight / (float) imgWidth;
float newContainerHeight, newContainerWidth;

if (h2w > 1) {
    // height is greater than width
    if (ch2cw) {
        newContainerWidth = (float) containerWidth;
        newContainerHeight = newContainerWidth * h2w;
    } else {
        newContainerHeight = (float) containerHeight;
        newContainerWidth = newContainerHeight / h2w;
    }
} else {
    // width is greater than height
    if (ch2cw) {
        newContainerWidth = (float) containerWidth;
        newContainerHeight = newContainerWidth / h2w; 
    } else {
        newContainerWidth = (float) containerHeight;
        newContainerHeight = newContainerWidth * h2w;       
    }
}
Bitmap copy = Bitmap.createScaledBitmap(bmp, (int) newContainerWidth, (int) newContainerHeight, false);
imageView.setBackgroundDrawable(new BitmapDrawable(copy));
LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
imageView.setLayoutParams(params);
imageView.setMaxHeight((int) newContainerHeight);
imageView.setMaxWidth((int) newContainerWidth);

In the code snippet above is bmp the Bitmap object that is to be shown and imageView is the ImageView object

An important thing to note is the change of the layout parameters. This is necessary because setMaxHeight and setMaxWidth will only make a difference if the width and height are defined to wrap the content, not to fill the parent. Fill parent on the other hand is the desired setting at the beginning, because otherwise containerWidth and containerHeight will both have values equal to 0. So, in your layout file you will have something like this for your ImageView:

...
<ImageView android:id="@+id/my_image_view"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"/>
...
Hyperon answered 8/3, 2012 at 14:38 Comment(0)
H
4

This is not the most performant solution, but as somebody suggested instead of background you can create FrameLayout or RelativeLayout and use ImageView as pseudo background - other elements will be position simply above it:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

    <ImageView
        android:id="@+id/ivBackground"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:scaleType="fitStart"
        android:src="@drawable/menu_icon_exit" />

    <Button
        android:id="@+id/bSomeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="61dp"
        android:layout_marginTop="122dp"
        android:text="Button" />

</RelativeLayout>

The problem with ImageView is that only scaleTypes available are: CENTER, CENTER_CROP, CENTER_INSIDE, FIT_CENTER,FIT_END, FIT_START, FIT_XY, MATRIX (http://etcodehome.blogspot.de/2011/05/android-imageview-scaletype-samples.html)

and to "scale the background image (keeping its aspect ratio)" in some cases, when you want an image to fill the whole screen (for example background image) and aspect ratio of the screen is different than image's, the necessary scaleType is kind of TOP_CROP, because:

CENTER_CROP centers the scaled image instead of aligning the top edge to the top edge of the image view and FIT_START fits the screen height and not fill the width. And as user Anke noticed FIT_XY doesn't keep aspect ratio.

Gladly somebody has extended ImageView to support TOP_CROP

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

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

    public ImageViewScaleTypeTopCrop(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setup();
    }

    private void setup() {
        setScaleType(ScaleType.MATRIX);
    }

    @Override
    protected boolean setFrame(int frameLeft, int frameTop, int frameRight, int frameBottom) {

        float frameWidth = frameRight - frameLeft;
        float frameHeight = frameBottom - frameTop;

        if (getDrawable() != null) {

            Matrix matrix = getImageMatrix();
            float scaleFactor, scaleFactorWidth, scaleFactorHeight;

            scaleFactorWidth = (float) frameWidth / (float) getDrawable().getIntrinsicWidth();
            scaleFactorHeight = (float) frameHeight / (float) getDrawable().getIntrinsicHeight();

            if (scaleFactorHeight > scaleFactorWidth) {
                scaleFactor = scaleFactorHeight;
            } else {
                scaleFactor = scaleFactorWidth;
            }

            matrix.setScale(scaleFactor, scaleFactor, 0, 0);
            setImageMatrix(matrix);
        }

        return super.setFrame(frameLeft, frameTop, frameRight, frameBottom);
    }

}

https://mcmap.net/q/161220/-imageview-scaling-top_crop

Now IMHO would be perfect if somebody wrote custom Drawable which scales image like that. Then it could be used as background parameter.


Reflog suggests to prescale drawable before using it. Here is instruction how to do it: Java (Android): How to scale a drawable without Bitmap? Although it has disadvantage, that upscaled drawable/bitmap will use more RAM, while scaling on the fly used by ImageView doesn't require more memory. Advantage could be less processor load.

Hellen answered 21/6, 2013 at 9:33 Comment(1)
Nice one! Does exactly what I was after: background image to fill whole screen by increasing the width proportionally and cropping off the bottom of the image.Kagu
C
4

The Below code make the bitmap perfectly with same size of the imageview. Get the bitmap image height and width and then calculate the new height and width with the help of imageview's parameters. That give you required image with best aspect ratio.

int bwidth=bitMap1.getWidth();
int bheight=bitMap1.getHeight();
int swidth=imageView_location.getWidth();
int sheight=imageView_location.getHeight();
new_width=swidth;
new_height = (int) Math.floor((double) bheight *( (double) new_width / (double) bwidth));
Bitmap newbitMap = Bitmap.createScaledBitmap(bitMap1,new_width,new_height, true);
imageView_location.setImageBitmap(newbitMap)
Corley answered 15/1, 2014 at 12:19 Comment(0)
B
3

What Dweebo proposed works. But in my humble opinion it is unnecessary. A background drawable scales well by itself. The view should have fixed width and height, like in the following example:

 < RelativeLayout 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@android:color/black">

    <LinearLayout
        android:layout_width="500dip"
        android:layout_height="450dip"
        android:layout_centerInParent="true"
        android:background="@drawable/my_drawable"
        android:orientation="vertical"
        android:padding="30dip"
        >
        ...
     </LinearLayout>
 < / RelativeLayout>
Battologize answered 11/4, 2012 at 16:43 Comment(1)
My understanding is that a background will expand to fill a view, but not shrink.Turpitude
G
1

One option to try is to put the image in the drawable-nodpi folder and set background of a layout to the drawable resource id.

This definitely works with scaling down, I haven't tested with scaling up though.

Gaynell answered 17/7, 2012 at 15:9 Comment(0)
K
0

Kotlin:

If you needed to draw a bitmap in a View, scaled to FIT.

You can do the proper calculations to set bm the height equal to the container and adjust width, in the case bm width to height ratio is less than container width to height ratio, or the inverse in the opposite scenario.

Images:

Example Image 1

Example Image 2

// binding.fragPhotoEditDrawCont is the RelativeLayout where is your view
// bm is the Bitmap
val ch = binding.fragPhotoEditDrawCont.height
val cw = binding.fragPhotoEditDrawCont.width
val bh = bm.height
val bw = bm.width
val rc = cw.toFloat() / ch.toFloat()
val rb = bw.toFloat() / bh.toFloat()

if (rb < rc) {
    // Bitmap Width to Height ratio is less than Container ratio
    // Means, bitmap should pin top and bottom, and have some space on sides.
    //              _____          ___
    // container = |_____|   bm = |___| 
    val bmHeight = ch - 4 //4 for container border
    val bmWidth = rb * bmHeight //new width is bm_ratio * bm_height
    binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth.toInt(), bmHeight)
}
else {
    val bmWidth = cw - 4 //4 for container border
    val bmHeight = 1f/rb * cw
    binding.fragPhotoEditDraw.layoutParams = RelativeLayout.LayoutParams(bmWidth, bmHeight.toInt())
}
Klaipeda answered 15/3, 2019 at 20:52 Comment(0)
R
-5

you'll have to pre-scale that drawable before you use it as a background

Recipe answered 10/9, 2009 at 14:37 Comment(4)
Is this really the only option? Is there no type of container to place the ImageView in which can specify display size?Coruscation
I am also having trouble with SurfaceViewDkl
There is nothing like aspect fit, aspect fill, top left, top etc like on iOS? That sucks..Elaelaborate
As @Aleksej said, an untrue answer.Minneapolis
H
-5

You can use one of following:

android:gravity="fill_horizontal|clip_vertical"

Or

android:gravity="fill_vertical|clip_horizontal"

Holo answered 19/1, 2015 at 13:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.