Dandre Allison
Dandre Allison

Reputation: 6035

Where'd padding go, when setting background Drawable?

I have this issue on my EditText and Button views, where I have a nice padding for them to space away from the text, but when I change the background with setBackgroundDrawable or setBackgroundResource that padding is lost forever.

Upvotes: 98

Views: 31656

Answers (18)

goemic
goemic

Reputation: 1339

Unfortunately the method public void setBackground(Drawable background) in View.java resets the previously defined padding of a View, as mentioned in the java docs:

Set the background to a given Drawable, or remove the background. If the background has padding, this View's padding is set to the background's padding. However, when a background is removed, this View's padding isn't touched. If setting the padding is desired, please use setPadding(int, int, int, int).

In order to keep the View's padding you can do something like this:

fun View.setBackgroundWithPreviousPadding(@DrawableRes value: Int) {
  val paddingBottom = this.paddingBottom
  val paddingStart = this.paddingStart
  val paddingEnd = this.paddingEnd
  val paddingTop = this.paddingTop

  background = AppCompatResources.getDrawable(this.context, value)
  setPaddingRelative(paddingStart, paddingTop, paddingEnd, paddingBottom)
}

Basically this answer is a mix of the other answers but I resolved all deprecation warnings and added some background information. (21. June 2024)

Upvotes: 0

NataTse
NataTse

Reputation: 411

maybe it will be relevant for someone

small hack using drawable in selector doesn't remove padding

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/anything" />
</selector>

Upvotes: 0

Displee
Displee

Reputation: 730

In my case I had a drawable and was so stupid I didn't see the paddings were all set to 0 in the xml.

Upvotes: 0

Achraf Amil
Achraf Amil

Reputation: 1375

Just to explain what's happening :

It's actually a feature. Layout drawables you might use as backgrounds can define a padding this way :

<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:paddingRight="8dp"
    >
    ...
</layer-list>

This padding will be set along with the new drawable background. When there's no padding, the default value is 0.

More info from an email written by Romain Guy: https://www.mail-archive.com/[email protected]/msg09595.html

Upvotes: 3

android developer
android developer

Reputation: 116040

Combining the solutions of all, I wrote one in Kotlin:

fun View.setViewBackgroundWithoutResettingPadding(@DrawableRes backgroundResId: Int) {
    val paddingBottom = this.paddingBottom
    val paddingStart = ViewCompat.getPaddingStart(this)
    val paddingEnd = ViewCompat.getPaddingEnd(this)
    val paddingTop = this.paddingTop
    setBackgroundResource(backgroundResId)
    ViewCompat.setPaddingRelative(this, paddingStart, paddingTop, paddingEnd, paddingBottom)
}

fun View.setViewBackgroundWithoutResettingPadding(background: Drawable?) {
    val paddingBottom = this.paddingBottom
    val paddingStart = ViewCompat.getPaddingStart(this)
    val paddingEnd = ViewCompat.getPaddingEnd(this)
    val paddingTop = this.paddingTop
    ViewCompat.setBackground(this, background)
    ViewCompat.setPaddingRelative(this, paddingStart, paddingTop, paddingEnd, paddingBottom)
}

Upvotes: 2

Salman
Salman

Reputation: 624

I found another solution. I was facing the similar problem with Buttons. Eventually, i added:

android:scaleX= "0.85"
android:scaleY= "0.85"

it worked for me. The default padding is almost the same.

Upvotes: 0

The Dirty Calvinist
The Dirty Calvinist

Reputation: 538

I was able to wrap the element inside another layout, in this case, a FrameLayout. That enabled me to change the background on the FrameLayout without destroying the padding, which is on the contained RelativeLayout.

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:id="@+id/commentCell"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:background="@drawable/comment_cell_bg_single" >

    <RelativeLayout android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    android:padding="20dp" >

    <ImageView android:id="@+id/sourcePic"
               android:layout_height="75dp"
               android:layout_width="75dp"
               android:padding="5dp"
               android:background="@drawable/photoframe" 
     />
...

The other option is to set it programmatically after setting the background Drawable as suggested above. Just make sure to calculate the pixels to correct for the resolution of the device.

Upvotes: 14

My solution was to extend the view (in my case an EditText) and override setBackgroundDrawable() and setBackgroundResource() methods:

// Stores padding to avoid padding removed on background change issue
public void storePadding(){
    mPaddingLeft = getPaddingLeft();
    mPaddingBottom = getPaddingTop();
    mPaddingRight = getPaddingRight();
    mPaddingTop = getPaddingBottom();
}

// Restores padding to avoid padding removed on background change issue
private void restorePadding() {
    this.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
}

@Override
public void setBackgroundResource(@DrawableRes int resId) {
    storePadding();
    super.setBackgroundResource(resId);
    restorePadding();
}

@Override
public void setBackgroundDrawable(Drawable background) {
    storePadding();
    super.setBackgroundDrawable(background);
    restorePadding();
}

Upvotes: 1

cutiko
cutiko

Reputation: 10537

Most of answers are correct but should handle the background setting correctly.

First get the padding of your view

//Here my view has the same padding in all directions so I need to get just 1 padding
int padding = myView.getPaddingTop();

Then set the background

//If your are supporting lower OS versions make sure to verify the version
if(android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.JELLY_BEAN) {
    //getDrawable was deprecated so use ContextCompat                            
    myView.setBackgroundDrawable(ContextCompat.getDrawable(context, R.drawable.bg_accent_underlined_white));
} else {
    myView.setBackground(ContextCompat.getDrawable(context, R.drawable.bg_accent_underlined_white));
}

Then set the padding the view had before the background change

myView.setPadding(padding, padding, padding, padding);

Upvotes: 0

Anudeep Samaiya
Anudeep Samaiya

Reputation: 1968

I use this pretty easy workaround I define a accentColor in my style.xml like below

<item name="colorAccent">#0288D1</item>

and then I use the either of following styles in my Button tags

style="@style/Base.Widget.AppCompat.Button.Colored"
style="@style/Base.Widget.AppCompat.Button.Small"

for Example :

<Button
    android:id="@+id/btnLink"
    style="@style/Base.Widget.AppCompat.Button.Colored"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/tvDescription"
    android:textColor="@color/textColorPrimary"
    android:text="Visit Website" />

<Button
    android:id="@+id/btnSave"
    style="@style/Base.Widget.AppCompat.Button.Small"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@id/tvDescription"
    android:layout_toRightOf="@id/btnLink"
    android:textColor="@color/textColorPrimaryInverse"
    android:text="Save" />

Upvotes: -1

Andrew G
Andrew G

Reputation: 1557

Backward compatable version of cottonBallPaws's answer

/** 
  * Sets the background for a view while preserving its current     padding. If the background drawable 
  * has its own padding, that padding will be added to the current padding. 
 *  
 * @param view View to receive the new background. 
 * @param backgroundDrawable Drawable to set as new background. 
 */ 
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@SuppressWarnings("deprecation")
public static void setBackgroundAndKeepPadding(View view, Drawable backgroundDrawable) {
    Rect drawablePadding = new Rect();
    backgroundDrawable.getPadding(drawablePadding);
    int top = view.getPaddingTop() + drawablePadding.top;
    int left = view.getPaddingLeft() + drawablePadding.left;
    int right = view.getPaddingRight() + drawablePadding.right;
    int bottom = view.getPaddingBottom() + drawablePadding.bottom;

    int sdk = android.os.Build.VERSION.SDK_INT;
    if(sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
        view.setBackgroundDrawable(backgroundDrawable);
    } else {
        view.setBackground(backgroundDrawable);
    }
    view.setPadding(left, top, right, bottom);
}

Upvotes: 2

user3104023
user3104023

Reputation: 1

just change lib to v7:22.1.0 in android studio like this compile 'com.android.support:appcompat-v7:22.1.0'

Upvotes: 0

Torsten
Torsten

Reputation: 11

For common searcher,

just add setPadding after setBackgroudDrawable. When you change your drawable, you have to call setPadding again.

Like:

view.setBackgroundDrawable(backgroundDrawable);
view.setPadding(x, x, x, x);

The cleanest way is define your paddings inside a xml-drawable which points to the drawable-image-file

Greatings

Upvotes: 1

Oliver Jonas
Oliver Jonas

Reputation: 1248

Here an improved version of cottonBallPaws' setBackgroundAndKeepPadding. This maintains the padding even if you call the method multiple times:

/**
 * Sets the background for a view while preserving its current padding. If the background drawable
 * has its own padding, that padding will be added to the current padding.
 */
public static void setBackgroundAndKeepPadding(View view, Drawable backgroundDrawable) {

    Rect drawablePadding = new Rect();
    backgroundDrawable.getPadding(drawablePadding);

    // Add background padding to view padding and subtract any previous background padding
    Rect prevBackgroundPadding = (Rect) view.getTag(R.id.prev_background_padding);
    int left = view.getPaddingLeft() + drawablePadding.left -
            (prevBackgroundPadding == null ? 0 : prevBackgroundPadding.left);
    int top = view.getPaddingTop() + drawablePadding.top -
            (prevBackgroundPadding == null ? 0 : prevBackgroundPadding.top);
    int right = view.getPaddingRight() + drawablePadding.right -
            (prevBackgroundPadding == null ? 0 : prevBackgroundPadding.right);
    int bottom = view.getPaddingBottom() + drawablePadding.bottom -
            (prevBackgroundPadding == null ? 0 : prevBackgroundPadding.bottom);
    view.setTag(R.id.prev_background_padding, drawablePadding);

    view.setBackgroundDrawable(backgroundDrawable);
    view.setPadding(left, top, right, bottom);
}

You need to define a resource id via values/ids.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="prev_background_padding" type="id"/>
</resources>

Upvotes: -1

LukeWaggoner
LukeWaggoner

Reputation: 8909

I had this problem in a TextView, so I subclassed TextView and made an Override method of the TextView.setBackgroundResource(int resid) method. Like this:

@Override
public void setBackgroundResource(int resid) {
    int pl = getPaddingLeft();
    int pt = getPaddingTop();
    int pr = getPaddingRight();
    int pb = getPaddingBottom();

    super.setBackgroundResource(resid);

    this.setPadding(pl, pt, pr, pb);
}

This way, it gets the padding of the item before it sets the resource, but doesn't actually mess with the original functionality of the method, other than keeping the padding.

Upvotes: 13

Matt McMinn
Matt McMinn

Reputation: 16311

What I found was adding a 9 patch as a background resource reset the padding - although interestingly if I added a color, or non-9 patch image, it didn't. The solution was to save the padding values before the background gets added, then set them again afterwards.

private EditText value = (EditText) findViewById(R.id.value);

int pL = value.getPaddingLeft();
int pT = value.getPaddingTop();
int pR = value.getPaddingRight();
int pB = value.getPaddingBottom();

value.setBackgroundResource(R.drawable.bkg);
value.setPadding(pL, pT, pR, pB);

Upvotes: 103

cottonBallPaws
cottonBallPaws

Reputation: 21620

Haven't tested this super thoroughly, but this method might be of use:

    /**
 * Sets the background for a view while preserving its current padding. If the background drawable
 * has its own padding, that padding will be added to the current padding.
 * 
 * @param view View to receive the new background.
 * @param backgroundDrawable Drawable to set as new background.
 */
public static void setBackgroundAndKeepPadding(View view, Drawable backgroundDrawable) {
    Rect drawablePadding = new Rect();
    backgroundDrawable.getPadding(drawablePadding);
    int top = view.getPaddingTop() + drawablePadding.top;
    int left = view.getPaddingLeft() + drawablePadding.left;
    int right = view.getPaddingRight() + drawablePadding.right;
    int bottom = view.getPaddingBottom() + drawablePadding.bottom;

    view.setBackgroundDrawable(backgroundDrawable);
    view.setPadding(left, top, right, bottom);
}

Use this instead of view.setBackgroundDrawable(Drawable).

Upvotes: 10

achie
achie

Reputation: 4746

You can give some padding by using 9-patch images and defining the content area in the drawable. Check this

You can also set the padding in your layout from xml or programatically

xml padding tags

android:padding
android:paddingLeft
android:paddingRight
android:paddingTop
android:paddingBottom

You can try setting the padding manually from the code after you call the setBackgroundDrawable by calling setPadding on your EditText or Button Views

Upvotes: 1

Related Questions