46

I want to display TEXT and Icon on a Button.

+----------------------------+
|          Icon TEXT         |
+----------------------------+

I tried with

<Button 
      android:id="@+id/Button01" 
      android:layout_width="fill_parent"
      android:layout_height="wrap_content" 
      android:paddingLeft="40dip"
      android:text="TEXT"
      android:drawableLeft="@drawable/Icon" />

But Text and Icon is not in center.
My Text size varies, according to text size Icon and Text should get adjusted to center.

How should i do it?

iDev
  • 23,310
  • 7
  • 60
  • 85
User7723337
  • 11,857
  • 27
  • 101
  • 182
  • 1
    You could just have the icon and text in a single image and use an `ImageButton` object instead? – Will Tate Jan 27 '11 at 14:26
  • 2
    @willytate, Not if you want the text to be localized or otherwise dynamic, or if you had many different icons, etc. The question is a good one. It's sad that the Android GUI toolkit requires us to build a custom widget just to get text and an icon centered on a button. – NateS Apr 01 '11 at 05:27
  • maybe you shouldn't mark the answer below as accepted, since it doesn't solve the problem. – NateS Apr 01 '11 at 05:28
  • possible duplicate of [How to center icon and text in a android button with width set to "fill parent"](http://stackoverflow.com/questions/3634191/how-to-center-icon-and-text-in-a-android-button-with-width-set-to-fill-parent) – Ilmari Karonen Mar 02 '14 at 18:45

13 Answers13

33

You can fake it by making a more complex layout, but I'm not sure whether it's worth it. Here's something I hacked together:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
<Button
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    android:layout_alignTop="@+id/foreground"
    android:layout_alignBottom="@id/foreground"
    android:layout_alignRight="@id/foreground"
    android:layout_alignLeft="@id/foreground"
    android:onClick="clickedMe" />
   <RelativeLayout
        android:id="@id/foreground"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
    <TextView  
        android:id="@+id/button_text"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" 
        android:text="@string/hello" />
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_toLeftOf="@id/button_text"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        android:src="@drawable/icon" />
</RelativeLayout>
</RelativeLayout>

There might be a more concise way to do it. I tend to struggle getting RelativeLayout to do what I want sometimes. Note that you need to pay attention to the z-order (Button needs to appear first in the top level RelativeLayout) and you might need to adjust padding to get it to look the way you want.

Brian Cooley
  • 11,622
  • 4
  • 40
  • 39
  • 1
    This is just crazy. I'll give you the bounty for coming up with a solution, since I failed to specify level of insanity beforehand. ;) It sucks something so common has to be so difficult! I decided to write my own button class and manage my own TextView and ImageView. I used [TableLayout](http://code.google.com/p/table-layout/) (different from the (crappy) Android toolkit TableLayout) and it was relatively painless. On a somewhat related note, I totally hate the XML and all the built-in Android layouts. All Java code and TableLayout and I am much, much happier! – NateS Apr 05 '11 at 08:35
  • 2
    This throws a "ClassCast" exception. – Skone Aug 04 '11 at 17:08
31

Similar to some other approaches, I think a good solution is to extend Button and add the missing functionality by overriding its onLayout method:

public class CenteredIconButton extends Button {
    private static final int LEFT = 0, TOP = 1, RIGHT = 2, BOTTOM = 3;

    // Pre-allocate objects for layout measuring
    private Rect textBounds = new Rect();
    private Rect drawableBounds = new Rect();

    public CenteredIconButton(Context context) {
        this(context, null);
    }

    public CenteredIconButton(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.buttonStyle);
    }

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

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (!changed) return;

        final CharSequence text = getText();
        if (!TextUtils.isEmpty(text)) {
            TextPaint textPaint = getPaint();
            textPaint.getTextBounds(text.toString(), 0, text.length(), textBounds);
        } else {
            textBounds.setEmpty();
        }

        final int width = getWidth() - (getPaddingLeft() + getPaddingRight());

        final Drawable[] drawables = getCompoundDrawables();

        if (drawables[LEFT] != null) {
            drawables[LEFT].copyBounds(drawableBounds);
            int leftOffset =
                    (width - (textBounds.width() + drawableBounds.width()) + getRightPaddingOffset()) / 2 - getCompoundDrawablePadding();
            drawableBounds.offset(leftOffset, 0);
            drawables[LEFT].setBounds(drawableBounds);
        }

        if (drawables[RIGHT] != null) {
            drawables[RIGHT].copyBounds(drawableBounds);
            int rightOffset =
                    ((textBounds.width() + drawableBounds.width()) - width + getLeftPaddingOffset()) / 2 + getCompoundDrawablePadding();
            drawableBounds.offset(rightOffset, 0);
            drawables[RIGHT].setBounds(drawableBounds);
        }
    }
}

The sample only works for left and right drawables, but could be extended to adjust top and bottom drawables too.

atomicode
  • 581
  • 4
  • 11
  • 2
    I had to add `if (!changed) return;` in `onLayout()` because it was being called more than once. I've edited the answer to reflect this. – Austyn Mahoney Apr 09 '14 at 17:35
  • @AustynMahoney Good idea to reuse the objects instead of creating them every layout. I re-edited the base class to reflect the original question... – atomicode Apr 13 '14 at 09:00
  • 1
    Nice solution, works well for me. Just don't forget to add `android:gravity=center` on the XML because this code only handles drawable positioning. – akhy Aug 07 '14 at 08:47
  • Why won't SO let me give this a +10?? This saved me a ton of time, I had the requirement of centered the drawable left icon and text, however I had to use a button because it was part of a custom group of buttons so it would've been a pain to have one "button" be a viewgroup. Thanks!! – Justin Sep 17 '14 at 17:28
  • 2
    Oh, and here's the same code in Xamarin in case anyone else wants it... https://gist.github.com/justintoth/bbe22db3375dc1764126 – Justin Sep 17 '14 at 17:29
  • Using this code the image does not appear, any reason? – Paulo Rodrigues Oct 17 '14 at 14:16
  • 9
    This worked almost perfectly for me. I did run into a problem when the CenteredIConButton was placed into a listview. This caused the icon to shift farther and farther to the side so it was no longer lined up with the correct side of the text. The problem was the fact that the drawableBounds uses the offset which keeps pushing it over. The solution was to replace this line `drawableBounds.offset(leftOffset, 0);` with `drawableBounds.set(leftOffset, drawableBounds.top, leftOffset + drawableBounds.width(), drawableBounds.bottom);` – John S Jan 09 '15 at 02:59
  • @John S. Thanks for the extra comment. I had the same issue but I occurred after some of the components where updated in the scrollview that the buttons where contained in. – DroidT Jun 17 '15 at 20:23
  • Nice solution, but you have to make drawablePadding extra larger on lollipop since text is all in caps, otherwise the icon would be on top of the text and not at the start of it. – Silvia H Aug 14 '15 at 21:08
17

How about this one?

<?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="@color/lovely_color"
    android:clickable="true"
    android:onClick="clickHandler">

       <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="no?"
            android:textColor="@color/white"
            android:layout_centerHorizontal="true"
            android:drawableLeft="@drawable/lovely_icon"
            android:drawablePadding="10dp"
            android:padding="10dp"
            android:gravity="center"
            android:textSize="21sp"/>

</RelativeLayout>
Can Elmas
  • 487
  • 1
  • 7
  • 14
6

This should work

<LinearLayout        

android:layout_width="fill_parent"        
android:layout_height="wrap_content" 
android:gravity="center_horizontal">    
<TextView          
    android:id="@+id/button_text"        
    android:layout_width="wrap_content"         
    android:layout_height="wrap_content"        
    android:layout_centerInParent="true"        
     android:text="hello" />    
 <ImageView        
     android:layout_width="wrap_content"        
     android:layout_height="wrap_content"
     android:paddingBottom="10dip"
/>
</LinearLayout>
Anoop
  • 23,044
  • 10
  • 62
  • 76
  • 1
    Yes, but you can assign this parameters to LinearLayout: `android:clickable="true"` `android:onClick="viewOnClick"` And you habe a nice button :) – Chronos Dec 13 '11 at 17:20
5

How about using a SpannableString as the text with an ImageSpan?

Button myButton = ...
SpannableString ss = new SpannableString(" " + getString(R.string.my_button_text));
Drawable d = getResources().getDrawable(R.drawable.myIcon);
d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
ImageSpan span = new ImageSpan(d, DynamicDrawableSpan.ALIGN_BOTTOM);
ss.setSpan(span, 0, 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
myButton.setText(ss);
Matt Accola
  • 4,090
  • 4
  • 28
  • 37
1

The easy way (albeit not perfect) is to set the paddingRight to the same width as your icon.

ShibbyUK
  • 1,501
  • 9
  • 12
1
<com.google.android.material.button.MaterialButton
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/your_text"
    app:icon="@drawable/ic_example"
    app:iconGravity="textStart"/>
1

You can just set a padding depending on button size and image size:

Button button1 = null;
//initialize button….
ViewGroup.LayoutParams params = button1.getLayoutParams();
int btn1Width = ((int) (0.33 * (double)ecranWidth));
params.width = btn1Width;
button1.setLayoutParams(params);
button1.setPadding((btn1Width/2-9), 0, 0, 0);
//where (btn1Width/2-9)   =   size of button divided on 2 minux half size of icon… 
Andriy
  • 11
  • 1
0

This is what I did... It can be improved. The text is centered and the icon is to the left. So they both aren't centered as a group.

public class CustomButton extends Button
{
    Rect r = new Rect();
    private Drawable buttonIcon = null;
    private int textImageSeparation = 10;

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

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

    public CustomButton(Context context)
    {
        super(context);
    }

    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);


        Drawable icon = getButtonIcon();
        if(icon != null)
        {
            int drawableHeight = icon.getIntrinsicHeight();
            int drawableWidth = icon.getIntrinsicWidth();
            if(icon instanceof BitmapDrawable)
            {
                Bitmap bitmap = ((BitmapDrawable)icon).getBitmap();
                drawableWidth = (int) AndroidScreenUtils.dipToPixels(bitmap.getWidth());
                drawableHeight = (int) AndroidScreenUtils.dipToPixels(bitmap.getHeight());
            }
            else
            {
                drawableWidth = (int) AndroidScreenUtils.dipToPixels(icon.getIntrinsicWidth());
                drawableHeight = (int) AndroidScreenUtils.dipToPixels(icon.getIntrinsicHeight());
            }
            float textWidth = getLayout().getPaint().measureText(getText().toString());
            float left = ((getWidth() - textWidth) / 2) - getTextImageSeparation() - drawableWidth;

            int height = getHeight();
            int top = (height - drawableHeight) /2;
            int right = (int) (left + drawableWidth);
            int bottom = top + drawableHeight;
            r.set((int) left, top, right, bottom);
            icon.setBounds(r);
            icon.draw(canvas);
        }
    }

    private Drawable getButtonIcon()
    {
        return buttonIcon;
    }

    public void setButtonIcon(Drawable buttonIcon)
    {
        this.buttonIcon = buttonIcon;
    }

    private int getTextImageSeparation()
    {
        return textImageSeparation;
    }

    public void setTextImageSeparation(int dips)
    {
        this.textImageSeparation = (int) AndroidScreenUtils.dipToPixels(dips);
    }



}
Cal
  • 1,625
  • 4
  • 20
  • 30
0
<LinearLayout
        style="@style/Sonnen.Raised.Button.Transparent.LightBlueBorder"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="30dp"
        android:orientation="horizontal"
        android:padding="20dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:drawableLeft="@drawable/refresh"
            android:drawablePadding="10dp"
            android:drawableStart="@drawable/refresh"
            android:gravity="center"
            android:text="@string/generic_error_button_text"
            android:textColor="@color/dark_sky_blue"
            android:textSize="20sp"/>

    </LinearLayout>
RogerParis
  • 1,549
  • 1
  • 13
  • 24
0

I made a custom component to solve this problem.

Component class:

class CustomImageButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
    defStyleRes: Int = 0
) : RelativeLayout(context, attrs, defStyleAttr, defStyleRes) {

    init {
        inflate(context, R.layout.custom_image_button, this)

        // Load the styled attributes and set their properties
        val typedArray = context.obtainStyledAttributes(
            attrs,
            R.styleable.CustomImageButton, defStyleAttr, 0
        )

        val src = typedArray?.getDrawable(R.styleable.CustomImageButton_cib_src)
        val text = typedArray?.getText(R.styleable.CustomImageButton_cib_text)
        val contentDescription = typedArray?.getText(R.styleable.CustomImageButton_cib_contentDescription)

        ivIcon.setImageDrawable(src)
        tvText.text = text
        ivIcon.contentDescription = contentDescription

        typedArray?.recycle()
    }
}

Component XML:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:toos="http://schemas.android.com/tools"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_width="fill_parent"
        android:layout_height="@dimen/button_height">
    <Button
            android:id="@+id/bClick"
            android:layout_height="wrap_content"
            android:layout_width="fill_parent"
            android:layout_alignTop="@+id/foreground"
            android:layout_alignBottom="@id/foreground"
            android:layout_alignEnd="@id/foreground"
            android:layout_alignStart="@id/foreground"/>
    <RelativeLayout
            android:id="@id/foreground"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content">
        <TextView
                android:id="@+id/tvText"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:textColor="@color/textWhite"
                toos:text="Some text to test"
                toos:ignore="RelativeOverlap"/>
        <ImageView
                android:id="@+id/ivIcon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_toStartOf="@id/tvText"
                android:paddingTop="1dip"
                android:paddingBottom="1dip"
                android:src="@mipmap/some_image_to_test"
                toos:ignore="ContentDescription"/>
    </RelativeLayout>
</RelativeLayout>

The resources attributes, attrs.xml:

<declare-styleable name="CustomImageButton">
        <attr name="cib_src" format="reference"/>
        <attr name="cib_text" format="string"/>
        <attr name="cib_contentDescription" format="string"/>
    </declare-styleable>

Component use example:

<app.package.components.CustomImageButton
            android:id="@+id/cibMyImageButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            app:cib_src="@mipmap/my_image_to_put_in_the_button"
            app:cib_text="Some text to show in the button"
           app:cib_contentDescription="icon description"/>
Ângelo Polotto
  • 8,463
  • 2
  • 36
  • 37
-1

This is a hack, but worked for me with a negative margin:

<Button
    android:id="@+id/some_id"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:drawableStart="@drawable/some_drawable"
    android:drawablePadding="-118dp"
    android:paddingEnd="28dp"
    android:text="@string/some_string" />

A better way would probably be doing this in a custom view

alexy
  • 464
  • 5
  • 6
-5
android:layout_gravity="center_vertical|center_horizontal|center" >

eboix
  • 5,113
  • 1
  • 27
  • 38
fludent
  • 364
  • 3
  • 6
  • 3
    Seriously, what? `center` is the same as `center_vertical|center_horizontal`, as it's centered in both axes, adding all three makes no sense. Also, layout_gravity affects the View's position, not the content. – Kevin Coppock Jan 27 '11 at 14:29
  • 1
    They were ment as options, should have used "/", but the second part hit home. android:gravity is correct. – fludent Jan 27 '11 at 14:51
  • 1
    The gravity aligns the text within the space not taken up by the left drawable. This answer doesn't solve the problem. – NateS Apr 01 '11 at 05:27