How can I shrink the drawable on a button?
Asked Answered
K

16

88

how can I make the drawable on a button smaller? The icon is too big, actually higher than the button. This is the code I am using:

    <Button
    android:background="@drawable/red_button"
    android:drawableLeft="@drawable/s_vit"
    android:id="@+id/ButtonTest"
    android:gravity="left|center_vertical" 
    android:text="S-SERIES CALCULATOR"
    android:textColor="@android:color/white"
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:layout_marginLeft="25dp"
    android:layout_marginRight="25dp"
    android:drawablePadding="10dp">
    </Button>

The upper is how it should look, the lower how it looks right now.

The upper is how it should look, the lower how it looks right now.

I tried this but there is no image displayed. :-(

    Resources res = getResources();
    ScaleDrawable sd = new ScaleDrawable(res.getDrawable(R.drawable.s_vit), 0, 10f, 10f);
    Button btn = (Button) findViewById(R.id.ButtonTest);
    btn.setCompoundDrawables(sd.getDrawable(), null, null, null);
Kissie answered 24/9, 2011 at 8:56 Comment(3)
It would be better if you upload an image of what you are getting and what you are trying to do. Thanks.Canula
You can define your custom button in order to define size of drawables. See my answer here https://mcmap.net/q/120656/-compounddrawable-sizeRemonstrance
I know this is from 2011... but I actually prefer the look of the button with camera breaking out of the bounds of the button - I would have left it the way it was... just sayin' :)Boulanger
M
71

You should use a ImageButton and specify the image in android:src, and set android:scaletype to fitXY


Setting scaled drawable in code

Drawable drawable = getResources().getDrawable(R.drawable.s_vit);
drawable.setBounds(0, 0, (int)(drawable.getIntrinsicWidth()*0.5), 
                         (int)(drawable.getIntrinsicHeight()*0.5));
ScaleDrawable sd = new ScaleDrawable(drawable, 0, scaleWidth, scaleHeight);
Button btn = findViewbyId(R.id.yourbtnID);
btn.setCompoundDrawables(sd.getDrawable(), null, null, null); //set drawableLeft for example
Marinna answered 24/9, 2011 at 11:10 Comment(10)
If you set the drawable in code, you can resize it based on the button height and then set it.Marinna
Now the image is displayed but it does not get resized! I tried values between 0.1f and 10f. Any idea? Thanks for your help...Kissie
Try this drawable.setBounds(0, 0, drawable.getIntrinsicWidth()*0.5, drawable.getIntrinsicHeight()*0.5); Marinna
If this resizes the drawable, then you can remove the scaledrawable part and use the drawable directly.Marinna
Great, that works! However I need to cast the values to int. Thank you very much for your help.Kissie
In btn.setCompoundDrawables, by using sd.getDrawable(), it shows the scaled Drawable but some callbacks are not executed correctly like onBoundsChange. It works correctly by using the ScaleDrawable and calling .setLevel(10000) on itBischoff
Where is the padding? I want to put some padding programmatically :)Earlearla
-1 this does not work for me. The only code, that modifies the size of the image seems to be the calls to setBounds. sd.getDrawable() returns the original image.Hellcat
Save my life!!! Thank you......can be used for setCompoundDrawablesWithIntrinsicBounds or setCompoundDrawablesSabrasabre
Are we supposed to set our own values for scaleWidth and scaleHeight? Are they just descriptive?Algeria
C
91

I have found a very simple and effective XML solution that doesn't require ImageButton

Make a drawable file for your image as below and use it for android:drawableLeft

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

    <item
    android:id="@+id/half_overlay"
    android:drawable="@drawable/myDrawable"
    android:width="40dp"
    android:height="40dp"
    />

</layer-list>

You can set the image size with android:width and android:height properties.

This way you could at least get the same size for different screens.

The drawback is that it is not exactly like fitXY which would scale image width to fit X and scale image height accordingly.

Coloring answered 28/8, 2015 at 15:40 Comment(6)
This should be the accepted answer. If one does not want an ImageButton (e.g. because they want text, etc) then this answer allows for sized images within a Button with Text plus zero code.Metaphrast
I made a new drawable like this and then in android:drawableTop i added the new drawable. No matter what values for the width/height I set, it shows me the default one.Winfordwinfred
Does not seem to work for Android versions below Lollipop.Forcer
height, width only work in API level 23 and higher. Not supported backward.Midship
I agree, this is by far the best solutionBoehmite
What can we do for API level below 23? I'm targeting higher but my minimum SDK is 16 right now. ImageButton is not proper for me because I'd like text as well on the buttonSammie
M
71

You should use a ImageButton and specify the image in android:src, and set android:scaletype to fitXY


Setting scaled drawable in code

Drawable drawable = getResources().getDrawable(R.drawable.s_vit);
drawable.setBounds(0, 0, (int)(drawable.getIntrinsicWidth()*0.5), 
                         (int)(drawable.getIntrinsicHeight()*0.5));
ScaleDrawable sd = new ScaleDrawable(drawable, 0, scaleWidth, scaleHeight);
Button btn = findViewbyId(R.id.yourbtnID);
btn.setCompoundDrawables(sd.getDrawable(), null, null, null); //set drawableLeft for example
Marinna answered 24/9, 2011 at 11:10 Comment(10)
If you set the drawable in code, you can resize it based on the button height and then set it.Marinna
Now the image is displayed but it does not get resized! I tried values between 0.1f and 10f. Any idea? Thanks for your help...Kissie
Try this drawable.setBounds(0, 0, drawable.getIntrinsicWidth()*0.5, drawable.getIntrinsicHeight()*0.5); Marinna
If this resizes the drawable, then you can remove the scaledrawable part and use the drawable directly.Marinna
Great, that works! However I need to cast the values to int. Thank you very much for your help.Kissie
In btn.setCompoundDrawables, by using sd.getDrawable(), it shows the scaled Drawable but some callbacks are not executed correctly like onBoundsChange. It works correctly by using the ScaleDrawable and calling .setLevel(10000) on itBischoff
Where is the padding? I want to put some padding programmatically :)Earlearla
-1 this does not work for me. The only code, that modifies the size of the image seems to be the calls to setBounds. sd.getDrawable() returns the original image.Hellcat
Save my life!!! Thank you......can be used for setCompoundDrawablesWithIntrinsicBounds or setCompoundDrawablesSabrasabre
Are we supposed to set our own values for scaleWidth and scaleHeight? Are they just descriptive?Algeria
V
11

Buttons do not resize their inner images.

My solution does not require code manipulation.

It uses a layout with TextView and ImageView.

The background of the layout should have the red 3d drawable.

You may need to define the android:scaleType xml attribute.

Example:

<LinearLayout
    android:id="@+id/list_item"
    android:layout_width="fill_parent"
    android:layout_height="50dp"
    android:padding="2dp" >

    <ImageView
        android:layout_width="50dp"
        android:layout_height="fill_parent"
        android:src="@drawable/camera" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:lines="1"
        android:gravity="center_vertical"
        android:text="Hello - primary" />

</LinearLayout>

BTW:

  1. Counting on different resolution icons may result in a non predictable UI (icon too big or too small)
  2. Text in textview (including in buttons) does not fill the component. This is an Android problem and I don't know how to solve it.
  3. You can use it as an include.

Good luck

Ventilator answered 24/12, 2011 at 10:52 Comment(1)
RE: TextViews, if I want the view to be as small as possible I set the padding for it to zero. It appears to have a default padding otherwise.Catachresis
I
6

Use a ScaleDrawable as Abhinav suggested.

The problem is that the drawable doesn't show then - it's some sort of bug in ScaleDrawables. you'll need to change the "level" programmatically. This should work for every button:

// Fix level of existing drawables
Drawable[] drawables = myButton.getCompoundDrawables();
for (Drawable d : drawables) if (d != null && d instanceof ScaleDrawable) d.setLevel(1);
myButton.setCompoundDrawables(drawables[0], drawables[1], drawables[2], drawables[3]);
Isotope answered 4/12, 2012 at 15:58 Comment(3)
Nice. I think I might be using this in the near future!Tempura
@zeh, now I found that it works if you run the above code before setContentView, otherwise it doesn't work. It could be better if you add this to your post.Coloring
For some reason, my above solution doesn't work on all android versions.Coloring
A
6

My DiplayScaleHelper, that works perfectly:

import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ScaleDrawable;
import android.widget.Button;

public class DisplayHelper {

  public static void scaleButtonDrawables(Button btn, double fitFactor) {
        Drawable[] drawables = btn.getCompoundDrawables();

        for (int i = 0; i < drawables.length; i++) {
            if (drawables[i] != null) {
                if (drawables[i] instanceof ScaleDrawable) {
                    drawables[i].setLevel(1);
                }
                drawables[i].setBounds(0, 0, (int) (drawables[i].getIntrinsicWidth() * fitFactor),
                    (int) (drawables[i].getIntrinsicHeight() * fitFactor));
                ScaleDrawable sd = new ScaleDrawable(drawables[i], 0, drawables[i].getIntrinsicWidth(), drawables[i].getIntrinsicHeight());
                if(i == 0) {
                    btn.setCompoundDrawables(sd.getDrawable(), drawables[1], drawables[2], drawables[3]);
                } else if(i == 1) {
                    btn.setCompoundDrawables(drawables[0], sd.getDrawable(), drawables[2], drawables[3]);
                } else if(i == 2) {
                    btn.setCompoundDrawables(drawables[0], drawables[1], sd.getDrawable(), drawables[3]);
                } else {
                    btn.setCompoundDrawables(drawables[0], drawables[1], drawables[2], sd.getDrawable());
                }
            }
        }
    }
}
Antennule answered 21/1, 2016 at 16:21 Comment(0)
H
4

You can call setBounds on the "compound" drawables to modify the size of the image.

Try this code to autosize the drawable of your button:

DroidUtils.scaleButtonDrawables((Button) findViewById(R.id.ButtonTest), 1.0);

defined by this function:

public final class DroidUtils {

    /** scale the Drawables of a button to "fit"
     *  For left and right drawables: height is scaled 
     *  eg. with fitFactor 1 the image has max. the height of the button.
     *  For top and bottom drawables: width is scaled: 
     *  With fitFactor 0.9 the image has max. 90% of the width of the button 
     *  */
    public static void scaleButtonDrawables(Button btn, double fitFactor) {
        Drawable[] drawables = btn.getCompoundDrawables();

        for (int i = 0; i < drawables.length; i++) {
            if (drawables[i] != null) {
                int imgWidth = drawables[i].getIntrinsicWidth();
                int imgHeight = drawables[i].getIntrinsicHeight();
                if ((imgHeight > 0) && (imgWidth > 0)) {    //might be -1
                    float scale;
                    if ((i == 0) || (i == 2)) { //left or right -> scale height
                        scale = (float) (btn.getHeight() * fitFactor) / imgHeight;
                    } else { //top or bottom -> scale width
                        scale = (float) (btn.getWidth() * fitFactor) / imgWidth;                    
                    }
                    if (scale < 1.0) {
                        Rect rect = drawables[i].getBounds();
                        int newWidth = (int)(imgWidth * scale);
                        int newHeight = (int)(imgHeight * scale);
                        rect.left = rect.left + (int)(0.5 * (imgWidth - newWidth)); 
                        rect.top = rect.top + (int)(0.5 * (imgHeight - newHeight));
                        rect.right = rect.left + newWidth;
                        rect.bottom = rect.top + newHeight;
                        drawables[i].setBounds(rect);
                    }
                }
            }
        }
    }
}

Be aware, that this may not be called in onCreate() of an activity, because button height and width are not (yet) available there. Call this in on onWindowFocusChanged() or use this solution to call the function.

Edited:

The first incarnation of this function did not work correctly. It used userSeven7s code to scale the image, but returning ScaleDrawable.getDrawable() does not seem to work (neither does returning ScaleDrawable) for me.

The modified code uses setBounds to provide the bounds for the image. Android fits the image into these bounds.

Hellcat answered 9/9, 2015 at 12:36 Comment(1)
Thank you that you said to not call it in onCreate().Thrust
Y
1

If you want to use 1 image and display it in different size, you can use scale drawable ( http://developer.android.com/guide/topics/resources/drawable-resource.html#Scale ).

Yulma answered 24/9, 2011 at 12:25 Comment(0)
R
1

I am doing it as below. This creates a 100x100 size image in the button independent of the input image.

drawable.bounds = Rect(0,0,100,100)
button.setCompoundDrawables(drawable, null, null, null)

Not using ScaleDrawable either. Not using button.setCompoundDrawablesRelativeWithIntrinsicBounds() solved my problem, as that seems to use intrinsic bounds (source image size) instead of the bounds you just set.

Reidreidar answered 2/4, 2019 at 21:21 Comment(0)
T
0

You can use different sized drawables that are used with different screen densities/sizes, etc. so that your image looks right on all devices.

See here: http://developer.android.com/guide/practices/screens_support.html#support

Tarazi answered 24/9, 2011 at 9:15 Comment(1)
I use the same image at different places. This is why I cannot resize it, it should be displayed in the size I want.Kissie
T
0

Did you try wrapping your image in a ScaleDrawable and then using it in your button?

Tempura answered 24/9, 2011 at 12:25 Comment(3)
Just tried this, however my ScaleDrawable does not display. :-( Even not in an ImageView. I took the sample code and just replaced the drawable. Any idea why this does not work?Kissie
I had the same problem. ScaleDrawable would be perfect, but it simply doesn't work. It seems to have something to do with "levels". More information here (and some workarounds that are less than perfect): #5508039Isotope
(edit) fixed it with some generic code! Solution here that still allows you to use ScaleDrawables: https://mcmap.net/q/195662/-how-can-i-shrink-the-drawable-on-a-buttonIsotope
D
0

Here the function which I created for scaling vector drawables. I used it for setting TextView compound drawable.

/**
 * Used to load vector drawable and set it's size to intrinsic values
 *
 * @param context Reference to {@link Context}
 * @param resId   Vector image resource id
 * @param tint    If not 0 - colour resource to tint the drawable with.
 * @param newWidth If not 0 then set the drawable's width to this value and scale 
 *                 height accordingly.
 * @return On success a reference to a vector drawable
 */
@Nullable
public static Drawable getVectorDrawable(@NonNull Context context,
                                         @DrawableRes int resId,
                                         @ColorRes int tint,
                                         float newWidth)
{
    VectorDrawableCompat drawableCompat =
            VectorDrawableCompat.create(context.getResources(), resId, context.getTheme());
    if (drawableCompat != null)
    {
        if (tint != 0)
        {
            drawableCompat.setTint(ResourcesCompat.getColor(context.getResources(), tint, context.getTheme()));
        }

        drawableCompat.setBounds(0, 0, drawableCompat.getIntrinsicWidth(), drawableCompat.getIntrinsicHeight());

        if (newWidth != 0.0)
        {
            float scale = newWidth / drawableCompat.getIntrinsicWidth();
            float height = scale * drawableCompat.getIntrinsicHeight();
            ScaleDrawable scaledDrawable = new ScaleDrawable(drawableCompat, Gravity.CENTER, 1.0f, 1.0f);
            scaledDrawable.setBounds(0,0, (int) newWidth, (int) height);
            scaledDrawable.setLevel(10000);
            return scaledDrawable;
        }
    }
    return drawableCompat;
}
Daughterly answered 11/10, 2016 at 10:1 Comment(0)
S
0
  • Using "BATCH DRAWABLE IMPORT" feature you can import custom size depending upon your requirement example 20dp*20dp

    • Now after importing use the imported drawable_image as drawable_source for your button

    • It's simpler this way enter image description here

Suprasegmental answered 3/1, 2017 at 7:59 Comment(2)
How did you get there?Vergos
This is a plugin i think. plugins.jetbrains.com/plugin/7658-android-drawable-importerLimitative
C
0

It is because you did not setLevel. after you setLevel(1), it will be display as u want

Chandelle answered 8/6, 2017 at 11:47 Comment(0)
H
0

I made a custom button class to achieve this.

CustomButton.java

public class CustomButton extends android.support.v7.widget.AppCompatButton {

    private Drawable mDrawable;

    public CustomButton(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.CustomButton,
                0, 0);

        try {
            float mWidth = a.getDimension(R.styleable.CustomButton_drawable_width, 0);
            float mHeight = a.getDimension(R.styleable.CustomButton_drawable_width, 0);

            Drawable[] drawables = this.getCompoundDrawables();
            Drawable[] resizedDrawable = new Drawable[4];

            for (int i = 0; i < drawables.length; i++) {
                if (drawables[i] != null) {
                    mDrawable = drawables[i];
                }
                resizedDrawable[i] = getResizedDrawable(drawables[i], mWidth, mHeight);
            }

            this.setCompoundDrawables(resizedDrawable[0], resizedDrawable[1], resizedDrawable[2], resizedDrawable[3]);
        } finally {
            a.recycle();
        }
    }

    public Drawable getmDrawable() {
        return mDrawable;
    }

    private Drawable getResizedDrawable(Drawable drawable, float mWidth, float mHeight) {
        if (drawable == null) {
            return null;
        }

        try {
            Bitmap bitmap;

            bitmap = Bitmap.createBitmap((int)mWidth, (int)mHeight, Bitmap.Config.ARGB_8888);

            Canvas canvas = new Canvas(bitmap);
            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
            drawable.draw(canvas);
            return drawable;
        } catch (OutOfMemoryError e) {
            // Handle the error
            return null;
        }
    }
}

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomButton">
        <attr name="drawable_width" format="dimension" />
        <attr name="drawable_height" format="dimension" />
    </declare-styleable>
</resources>

Usage in xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.MainActivity">

     <com.example.CustomButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:drawableTop="@drawable/ic_hero"
            android:text="Avenger"
            custom:drawable_height="10dp"
            custom:drawable_width="10dp" />

</RelativeLayout>
Haleakala answered 27/5, 2018 at 6:32 Comment(1)
Why do you create a Canvas in getResizedDrawable and draw into it if you are not going to do anything with it? The whole function could be reduced just to drawable.setBounds(0, 0, mWidth, mHeight).Preservative
J
0

Using Kotlin Extension

val drawable = ContextCompat.getDrawable(context, R.drawable.my_icon)
// or resources.getDrawable(R.drawable.my_icon, theme)
val sizePx = 25
drawable?.setBounds(0, 0, sizePx, sizePx)
//                            (left,     top,  right, bottom)
my_button.setCompoundDrawables(drawable, null, null, null)

I suggest creating an extension function on TextView (Button extends it) for easy reuse.

button.leftDrawable(R.drawable.my_icon, 25)

// Button extends TextView
fun TextView.leftDrawable(@DrawableRes id: Int = 0, @DimenRes sizeRes: Int) {
    val drawable = ContextCompat.getDrawable(context, id)
    val size = context.resources.getDimensionPixelSize(sizeRes)
    drawable?.setBounds(0, 0, size, size)
    this.setCompoundDrawables(drawable, null, null, null)
}
Jealousy answered 8/1, 2020 at 18:37 Comment(2)
The question was about Button, not TextView.Ashcraft
You should have read the answer. A Button extends TextView, so you can use this for both. Or change the extension to only extend a ButtonJealousy
Z
-1

I tried the techniques of this post but didnot find any of them so attractive. My solution was to use an imageview and textview and align the imageview top and bottom to the textview. This way I got the desired result. Here's some code:

<RelativeLayout
    android:id="@+id/relativeLayout1"
    android:layout_width="match_parent"
    android:layout_height="48dp" >


    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignTop="@+id/textViewTitle"
        android:layout_alignBottom="@+id/textViewTitle"
        android:src="@drawable/ic_back" />

    <TextView
        android:id="@+id/textViewBack"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/textViewTitle"
        android:layout_alignBottom="@+id/textViewTitle"
        android:layout_toRightOf="@+id/imageView1"
        android:text="Back"
        android:textColor="@color/app_red"
        android:textSize="@dimen/title_size" />
</RelativeLayout>
Zampardi answered 5/6, 2014 at 4:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.