106

I found this great thread describing how to "eat the cake and have it too", i.e. use image for a Button instead of ImageButton (which doesn't allow SetText(), resizing, etc.).

This is achieved by using the View attribute:

android:background="@drawable/bgimage"

The only problem with this is that it stretches the image to fit the button size.

Short of hard-coding a fixed button size (in pixels!), is there a way to tell Android not to stretch the background image at all and either crop or pad it?

Community
  • 1
  • 1
ef2011
  • 10,431
  • 12
  • 49
  • 67

11 Answers11

267

You can create an xml bitmap and use it as background for the view. To prevent stretching you can specify android:gravity attribute.

for example:

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

There are a lot of options you can use to customize the rendering of the image

http://developer.android.com/guide/topics/resources/drawable-resource.html#Bitmap

WM1
  • 451
  • 7
  • 14
Santosh
  • 13,251
  • 5
  • 24
  • 12
  • 3
    Using ImageViews is a workaround which isn't always a possible option - I believe this to be the proper solution :) – JakeP Jun 09 '14 at 09:24
  • 3
    I am setting background at run time. Is any solution to create bitmap at run time? – Vivek Kumar Srivastava Sep 30 '14 at 18:27
  • 1
    for me this was the best solution but removing the "gravity" attribute, which makes it match its default value "fill". This will allow image resize to fit the screen. Study @Santosh link for more info. Thanks! – Hugo Nov 12 '14 at 08:37
  • 12
    I only see one problem with this approach. If the drawable source is already larger than the container, this will clip the image (based on gravity) rather than resizing it, maintaining the aspect ratio, to fit the container. – Shashwat Black Nov 25 '14 at 05:40
  • Thanks Santosh. Much better than the RelativeLayout approach. set gravity to bottom|left let me put the pencil icon behind the value. Used a little right padding on the text. Kind liked having it behind but messed up when text got long. – maxweber May 06 '15 at 21:28
  • I'd give you 50 upvotes if I could. Tried for ages but was too dumb to see this one. – m02ph3u5 Apr 01 '16 at 15:22
  • Fantastic solution. Using this was the only way I could get my ScrollView to work properly. Thanks! – Mark Jul 05 '16 at 00:58
  • Does the `android:src` attribution can be changed by code to avoid make multiple xml files? – zionpi Dec 22 '16 at 07:48
  • How can I do this if it's not a resource? What if I want to do this programatically with an image taken by the Camera? – AdamMc331 Nov 03 '17 at 00:18
  • 2
    Appears a bit dated now. This only works for bitmaps, and doesn't work for Vector drawables. – The_Sympathizer Jul 27 '19 at 02:30
  • Problem is It won't cover the whole screen if the drawable height is shorter than the device's i.e., a tablet – 6rchid Aug 28 '23 at 20:48
29

You should use ImageView if you don't want it to stretch. Background images will always stretch to fit the view. You need to set it as a Drawable to force the image aspect to the object.

Otherwise, if you are sticking with the Button idea, then you will need to force the scaling in the button to prevent the image from stretching.

Code:

onCreate(Bundle bundle) {
  // Set content layout, etc up here

  // Now adjust button sizes
  Button b = (Button) findViewById(R.id.somebutton);
  int someDimension = 50; //50pixels
  b.setWidth(someDimension);
  b.setHeight(someDimension);
}
Lii
  • 11,553
  • 8
  • 64
  • 88
Dr.J
  • 1,220
  • 8
  • 9
  • 1
    Thanks + 1. `ImageView` looks interesting. I noticed it has an attribute that's not in `Button`: `android:cropToPadding`. I don't mind using an ImageView instead, as long as it has `setOnClickListener()` -- which it does. What will I be losing by switching from `Button` to `ImageView`? – ef2011 May 05 '11 at 18:46
  • 1
    BTW, the 2nd solution you suggested still stretches the image. – ef2011 May 05 '11 at 18:52
  • I ended up using fixed button size (in pixels!) but I am accepting your answer because the 1st suggestion looks like a working alternative solution. – ef2011 May 05 '11 at 21:57
  • 1
    @ef2011 For ImageView, don't set it as a background image, you have to set it as a `setDrawableResource` and then make sure `setAdjustViewBounds` is set to true – Dr.J May 06 '11 at 01:10
  • 10
    Downvoted for a lame solution. Correct solution here: http://stackoverflow.com/a/9362168/145046 – Aleks N. Feb 20 '12 at 13:52
27

Simply using ImageButton instead of Button fixes the problem.

<ImageButton android:layout_width="30dp"
             android:layout_height="30dp"
             android:src="@drawable/bgimage" />

and you can set

android:background="@null"

to remove button background if you want.

Quick Fix !! :-)

TENNO
  • 165
  • 2
  • 11
Adil Malik
  • 6,279
  • 7
  • 48
  • 77
13

I am using an ImageView in an RelativeLayout that overlays with my normal layout. No code required. It sizes the image to the full height of the screen (or any other layout you use) and then crops the picture left and right to fit the width. In my case, if the user turns the screen, the picture may be a tiny bit too small. Therefore I use match_parent, which will make the image stretch in width if too small.

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

    <ImageView
        android:id="@+id/main_backgroundImage"
        android:layout_width="match_parent"
        //comment: Stretches picture in the width if too small. Use "wrap_content" does not stretch, but leaves space

        android:layout_height="match_parent"
        //in my case I always want the height filled

        android:layout_alignParentTop="true"
        android:scaleType="centerCrop"
        //will crop picture left and right, so it fits in height and keeps aspect ratio

        android:contentDescription="@string/image"
        android:src="@drawable/your_image" />

    <LinearLayout
        android:id="@+id/main_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    </LinearLayout>

</RelativeLayout>
Mohammedsalim Shivani
  • 1,793
  • 3
  • 19
  • 30
Gunnar Bernstein
  • 6,074
  • 2
  • 45
  • 67
6

I had the same problem: you should only use a 9-patch image (.9.png) instead of your original picture.

Serge

5

Use draw9patch... included within Android Studio's SDK tools. You can define the stretchable areas of your image. Important parts are constrained and the image doesn't look all warped. A good demo on dra9patch is HERE

Use draw9patch to change your existing splash.png into new_splash.9.png, drag new_splash.9.png into the drawable-hdpi project folder ensure the AndroidManifest and styles.xml are proper as below:

AndroidManifest.xml:

<application
...
        android:theme="@style/splashScreenStyle"
>

styles.xml:

<style name="splashScreenStyle" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:windowBackground">@drawable/new_splash</item>
</style>
WM1
  • 451
  • 7
  • 14
4

I had a background image, not big in size, but with weird dimensions - therefore the stretching and bad performance. I made a method with parameters Context, a View and a drawable ID(int) that will match the device screen size. Use this in e.g a Fragments onCreateView to set the background.

public void setBackground(Context context, View view, int drawableId){
    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),drawableId);

    bitmap = Bitmap.createScaledBitmap(bitmap, Resources.getSystem().
             getDisplayMetrics().widthPixels,
             Resources.getSystem().getDisplayMetrics().heightPixels,
             true);

    BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), 
                                    bitmap);

    view.setBackground(bitmapDrawable);
}
ZooMagic
  • 616
  • 7
  • 15
1

Here's a version of Santosh's answer for programmatically-created buttons, without the need for a separate XML configuration:

Button button = new Button(getContext());
Bitmap backgroundBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.my_button);
BitmapDrawable backgroundDrawable = new BitmapDrawable(getResources(), backgroundBitmap);
backgroundDrawable.setGravity(Gravity.CENTER); // also LEFT, CENTER_VERTICAL, etc.
backgroundDrawable.setColorFilter(new PorterDuffColorFilter(Color.RED, PorterDuff.Mode.SRC_ATOP));
button.setBackground(backgroundDrawable);

I included the ColorFilter line since that works a little differently from buttons with a normal background image.

arlomedia
  • 8,534
  • 5
  • 60
  • 108
1

You can use a FrameLayout with an ImageView as the first child, then your normal layout as the second child:

<?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="match_parent">

  <ImageView
        android:id="@+id/background_image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="centerCrop"
        android:src="@drawable/your_drawable"/>

  <LinearLayout
        android:id="@+id/your_actual_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

  </LinearLayout>

</FrameLayout>
Adam Johns
  • 35,397
  • 25
  • 123
  • 176
0

One can use a plain ImageView in his xml and make it clickable (android:clickable="true")? You only have to use as src an image that has been shaped like a button i.e round corners.

Grigoreas P.
  • 2,452
  • 25
  • 19
0

The key is to set the drawable as the image of the button, not as a background. Like this:

rb.setButtonDrawable(R.drawable.whatever_drawable);
Jose L Ugia
  • 5,960
  • 3
  • 23
  • 26