How to have Image and Text Center within a Button
Asked Answered
M

13

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?

Myrtamyrtaceous answered 27/1, 2011 at 14:18 Comment(4)
You could just have the icon and text in a single image and use an ImageButton object instead?Meridithmeriel
@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.Margarettemargarida
maybe you shouldn't mark the answer below as accepted, since it doesn't solve the problem.Margarettemargarida
possible duplicate of How to center icon and text in a android button with width set to "fill parent"Anatol
P
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.

Poulter answered 2/4, 2011 at 17:37 Comment(2)
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 (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!Margarettemargarida
This throws a "ClassCast" exception.Mouldon
A
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.

Anneliese answered 2/3, 2014 at 17:28 Comment(9)
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.Dayton
@AustynMahoney Good idea to reuse the objects instead of creating them every layout. I re-edited the base class to reflect the original question...Anneliese
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.Lucillelucina
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!!Photomural
Oh, and here's the same code in Xamarin in case anyone else wants it... gist.github.com/justintoth/bbe22db3375dc1764126Photomural
Using this code the image does not appear, any reason?Brotherinlaw
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);Amber
@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.Wack
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.Neotropical
D
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>
Desmonddesmoulins answered 31/10, 2012 at 17:43 Comment(1)
actually , this one worked perfectly for me . you could even use a FrameLayout with gravity instead of a RelativeLayoutChap
E
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>
Ebsen answered 4/4, 2011 at 11:29 Comment(1)
Yes, but you can assign this parameters to LinearLayout: android:clickable="true" android:onClick="viewOnClick" And you habe a nice button :)Ho
A
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);
Amazonite answered 8/9, 2014 at 4:53 Comment(1)
It is always better to do it in XML, rather than writing code for it.Myrtamyrtaceous
T
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… 
Tumble answered 13/12, 2011 at 16:35 Comment(0)
D
1

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

Duda answered 4/7, 2012 at 14:32 Comment(2)
That only centers the text. The question is how to center the icon and text as a group.Overstep
I was trying to have BG image on left and text in center, and this is a genius solution.Moment
C
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"/>
Crystallo answered 9/3, 2021 at 20:25 Comment(1)
Easiest and the best solution. Add app:iconTint="@null" if you use an icon with color.Rayshell
O
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);
    }



}
Overstep answered 6/9, 2012 at 23:45 Comment(0)
R
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>
Resect answered 8/12, 2015 at 11:22 Comment(0)
C
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"/>
Claytor answered 12/12, 2018 at 15:27 Comment(0)
A
-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

Astonish answered 24/9, 2020 at 8:42 Comment(0)
D
-5
android:layout_gravity="center_vertical|center_horizontal|center" >

Dirigible answered 27/1, 2011 at 14:27 Comment(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.Tabbitha
They were ment as options, should have used "/", but the second part hit home. android:gravity is correct.Dirigible
The gravity aligns the text within the space not taken up by the left drawable. This answer doesn't solve the problem.Margarettemargarida

© 2022 - 2024 — McMap. All rights reserved.