55

I have a CheckBox that I want centered within its own boundaries, not pushed to the side. Probably easier demonstrated than explained:

enter image description here

Note that it isn't centered. Currently defined as:

<CheckBox
    android:id="@+id/checkbox_star"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:button="@drawable/btn_favorite"

    android:layout_gravity="center"
    android:minWidth="48dp" />

Nevermind the custom button drawable. It behaves the same with a vanilla CheckBox as well (the small check box behaves the same).

animuson
  • 53,861
  • 28
  • 137
  • 147
davidcesarino
  • 16,160
  • 16
  • 68
  • 109

14 Answers14

34

I believe the problem is that the Checkbox widget uses a regular TextView with the drawableLeft attribute, because it expects text to be shown as well. (This is why you see it centered vertically, but offset slightly to the left.)

If you simply want an image button with multiple states, I suggest using a ToggleButton with your custom images in a state list selector. Or you could create a custom class that extends ImageView and implements Checkable.

Sam
  • 86,580
  • 20
  • 181
  • 179
  • Bingo! Oh wow, thanks a bunch... easy and simple... I didn't think of ToggleButton! – davidcesarino Nov 16 '12 at 06:41
  • 18
    How `ToggleButton` can help with centering of image, if button size larger than image size? – Yura Shinkarev Jul 30 '14 at 12:59
  • 1
    For the last statement: "Or you could create a custom class that extends ImageView and implements Checkable.", you guys don't need to make it by yourself, just copy it directly from my answer here https://stackoverflow.com/a/37986765/3940133 – HendraWD Jul 23 '18 at 03:08
  • 3
    Just for clarification, CheckBox doesn't use TextView drawableLeft for its button. It draws the button on its own and only supports Gravity.BOTTOM or Gravity.CENTER_VERTICAL. https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/CompoundButton.java#424 – Alex Fu Nov 29 '18 at 15:27
  • You will also lose the animation effects associated with the Checkbox if you do it this way. – Jeff Padgett Oct 17 '19 at 15:56
  • 1
    @JeffPadgett then what is the better way to do it ? I am looking for the answer and still can't figure it out. – oyeraghib Aug 19 '22 at 14:25
  • @oyeraghib I honestly can't remember... been a while since I did this. – Jeff Padgett Aug 19 '22 at 22:36
23

I'm using MaterialCheckBox from material-components-android(or AppCompatCheckBox. They're similar).

I find that if a CheckBox doesn't have any text, setting its android:minWidth="0dp" and android:minHeight="0dp" can remove all paddings and center the drawable.

Dewey Reed
  • 4,353
  • 2
  • 27
  • 41
  • Holy hell, this is what I was looking for for hours! Thank you! – Leo Jun 25 '20 at 04:35
  • Why this works I have no idea, but you just saved me a bunch of time. Thanks! Is this documented anywhere? – Aloha Sep 18 '20 at 11:12
  • 2
    @PNDA I didn't find any docs. I simply tried every combination. – Dewey Reed Sep 18 '20 at 13:13
  • @SamChen I verify the solution with AS 4.1 and MDC 1.2.1, and it works. – Dewey Reed Dec 04 '20 at 01:44
  • 3
    While this answer works well, it has an issue: the view actually becomes smaller, and therefore the touch box becomes smaller as well. Material Design guidelines recommend touch targets to be no smaller than 48x48 dp (https://material.io/design/usability/accessibility.html#layout-and-typography). The default material checkbox is exactly that size, so it should likely not be made smaller. – Erik May 27 '21 at 13:27
  • I'm having the same problem as @Erik. Setting the `minHeight` & `minWidth` doesn't help if `padding` is greater than 0dp. – James Sep 16 '22 at 04:54
16

You can use a parent layout to achieve this :

<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center">

    <CheckBox
        android:id="@+id/checkbox_star"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Check box text" />
</LinearLayout>
Eldhose M Babu
  • 14,382
  • 8
  • 39
  • 44
  • 6
    Thanks for your reply, but that's exactly why I said "center [...] in itself" and not "center [...] in something else" ;-) , otherwise I think I'd have done that already. This issue is very relevant when you consider the clicking area of the widget. – davidcesarino Nov 16 '12 at 06:20
  • Then seperate the checkbox button and checkbox text as two controls if clicking area is your issue. Use checkbox along with a textview side by side. – Eldhose M Babu Nov 16 '12 at 06:28
  • 2
    I don't have (or want) any text, nor it solves the problem at hand. This is about creating a `CheckBox` larger than its drawable while positioning said drawable in the center and not in the left of the widget boundaries. What you said does not do that. – davidcesarino Nov 16 '12 at 06:41
  • This helps to center a CheckBox in the area of a parent Layout. I didn't even have to set the gravity for the CheckBox itself. Probably not the answer for the question asked, but it helped me to achieve what I was looking for. – Marcell Apr 13 '21 at 20:21
13
android:button="@null"
android:foreground="@drawable/btn_favorite" 
android:foregroundGravity="center"
AskNilesh
  • 67,701
  • 16
  • 123
  • 163
j__m
  • 9,392
  • 1
  • 32
  • 56
  • This works perfectly for my use case. Coupled with a drawable state selector for `android:background` this achieves a fully customized checkbox. – jhauberg Jan 11 '18 at 09:05
13

The only proper solution is to add insets to the image. Most of the solutions with "foreground" won't work below API 23.

btn_favorite_inset.xml in drawable dir

<inset xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/btn_favorite"
    android:insetLeft="6dp"
    android:insetTop="6dp"
    android:insetRight="6dp"
    android:insetBottom="6dp" />

Your layout file

<CheckBox
    android:id="@+id/checkbox_star"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:button="@drawable/btn_favorite_inset"
    android:layout_gravity="center"
    android:minWidth="48dp" />
tomrozb
  • 25,773
  • 31
  • 101
  • 122
9

This align problem can be solved by CheckableImageView, a custom View that extend ImageView or AppCompatImageView and implement Checkable directly. It also have other ImageView attributes.

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Checkable;
import android.widget.ImageView;

/**
 * @author hendrawd on 6/23/16
 */
public class CheckableImageView extends ImageView implements Checkable {

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

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

    private boolean mChecked = false;

    private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};

    @Override
    public int[] onCreateDrawableState(final int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
        if (isChecked())
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        return drawableState;
    }

    @Override
    public void setChecked(boolean checked) {
        if (mChecked != checked) {
            mChecked = checked;
            refreshDrawableState();
        }
    }

    @Override
    public boolean isChecked() {
        return mChecked;
    }

    @Override
    public void toggle() {
        setChecked(!mChecked);
    }

    @Override
    public void setOnClickListener(final OnClickListener l) {
        View.OnClickListener onClickListener = new OnClickListener() {
            @Override
            public void onClick(View v) {
                toggle();
                l.onClick(v);
            }
        };
        super.setOnClickListener(onClickListener);
    }
}

Just set your selector drawable in src property of your XML and the drawable state of check will follow automatically.

Example of use

<your.package.name.CheckableImageView
    android:id="@+id/some_id"
    android:layout_width="48dp"
    android:layout_height="48dp"
    android:src="@drawable/set_your_selector_here"
    android:padding="14dp"
    android:gravity="center" />

Selector example(put inside drawable folder with extension .xml)

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_cb_check" android:state_checked="true" android:state_focused="true" />
    <item android:drawable="@drawable/ic_cb_uncheck" android:state_checked="false" android:state_focused="true" />
    <item android:drawable="@drawable/ic_cb_uncheck" android:state_checked="false" />
    <item android:drawable="@drawable/ic_cb_check" android:state_checked="true" />
</selector>

Change ic_cb_check and ic_cb_uncheck with your preferred images.

This code also available at https://gist.github.com/hendrawd/661824a721c22b3244667379e9358b5f

HendraWD
  • 2,984
  • 2
  • 31
  • 45
3

By default, it has minHeight and minWidth. Setting them to 0dp will align it to center within its own boundary.


<CheckBox
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:minWidth="0dp"
    android:minHeight="0dp" />

spidey
  • 378
  • 3
  • 8
2

This will work correct as need required. Just small changes in background and button attributes. This code snippet is tested successfully. Hope this helps.

 <CheckBox
android:id="@+id/checkbox_star"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="@drawable/btn_favorite"
android:button="@color/transparent"
android:layout_gravity="center"
android:minWidth="48dp" />
Anant Shah
  • 3,744
  • 1
  • 35
  • 48
2

Just use following approach:

          <CheckBox
                  android:button="@null"                        
                  android:foreground="@drawable/checkbox_selector"
                  android:foregroundGravity="center"/>

It works well for me. But it works only for

targetSdkVersion >= Build.VERSION_CODES.M || view instanceof FrameLayout

ultraon
  • 2,220
  • 2
  • 28
  • 27
1

Do not use this code to bypass checkbox bug with center aligment for >SDK23 if you are going to use checkboxes in recyclerview.

<CheckBox
android:button="@null"
android:drawableLeft="@drawable/checkbox_selector"
android:drawableStart="@drawable/checkbox_selector"
/>

Checkboxes will not get checked in onBindViewHolder until next refresh if RV (example: scroll,..).

AndroidTank
  • 324
  • 4
  • 11
0

Set the android:width so that it only shows your checkbox, then center it using android:layout_gravity="center".

0

I have tried j__m's answer above, and it works, but not good enough.

In my case, I wanted to add paddings around the CheckBox's drawable, in order to increase the touch area. And j__m's answer makes it hard to adjust the UI.

I think a better solution is to create an inset drawable to wrap your original state drawable, which should be very easy and work perfectly.

Henry
  • 942
  • 3
  • 11
  • 21
0

In your code, you have set the height of checkbox to match_parent. Set it to wrap_content. This will solve your problem. Worked for me!

Problem:

<CheckBox
    android:id="@+id/checkbox_star"
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:button="@drawable/btn_favorite"

    android:layout_gravity="center"
    android:minWidth="48dp" />

Solution:

<CheckBox
    android:id="@+id/checkbox_star"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:button="@drawable/btn_favorite"

    android:layout_gravity="center"
    android:minWidth="48dp" />
Prajwal Waingankar
  • 2,534
  • 2
  • 13
  • 20
0

Its better to use layer list in this case , since above accepted solution will only work for device > Build.VERSION_CODES.M. Its better to use solution which is applicable for all scenarios

 <CheckBox
    android:id="@+id/mute_btn"
    android:layout_width="@dimen/_26sdp"
    android:layout_height="@dimen/_26sdp"
    android:layout_marginStart="@dimen/_10sdp"
    android:background="@drawable/mute_btn_layer_list"
    android:checked="false"
    android:button="@null"
    app:layout_constraintBottom_toBottomOf="@id/replay_btn"
    app:layout_constraintStart_toEndOf="@id/replay_btn"
    app:layout_constraintTop_toTopOf="@id/replay_btn" />

mute_btn_layer_list :

<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/corner_rounded_black_bg"/>
    <item android:drawable="@drawable/btn_mute"/>
</layer-list>

where drawble btn_mute is your drawable selector