70

I have a view which is used as an item in a ListView. In my custom adapter, I change the background of the view using View.setBackgroundResource() depending on the item's position in the list. (I have separate assets for the first and last items in the list.)

This sets the correct background image as expected, but it has the nasty side-effect that all the padding I'd set in the XML definition of the view is completely ignored.

(If I set the background drawable in the XML, and don't try to vary it at runtime in the adapter, the padding all works fine.)

How can I alter the background image, and retain the padding? Is this a bug?

EDIT it seems someone else has found the same problem here: Does changing the background also change the padding of a LinearLayout?

Community
  • 1
  • 1
Graham Borland
  • 57,578
  • 20
  • 131
  • 176

5 Answers5

106

I ran into this issue as well. Presumably you're using a LayerList resource drawable? That's what I was using. Unfortunately, I found no "real" way of fixing it, it seems like a bug in the code, but I didn't chase it down. However, I was lucky in the sense that I was setting the "buggy" background after my view had already been rendered properly, so it was just a matter of saving then restoring the padding values after the background is set, e.g:

  if(condition) {
    int bottom = theView.getPaddingBottom();
    int top = theView.getPaddingTop();
    int right = theView.getPaddingRight();
    int left = theView.getPaddingLeft();
    theView.setBackgroundResource(R.drawable.entry_bg_with_image);
    theView.setPadding(left, top, right, bottom);
  }

EDIT: As an alternative, you don't have to use the previous values of padding, you can also use a dimension value:

  int pad = resources.getDimensionPixelSize(R.dimen.linear_layout_padding);
  theView.setBackgroundResource(R.drawable.entry_bg_with_image);
  theView.setPadding(pad, pad, pad, pad);
dmon
  • 29,684
  • 6
  • 84
  • 92
  • That cures it, thanks. In my case the drawable is a nine-patch PNG. – Graham Borland May 04 '11 at 22:23
  • Interesting, then maybe it's more widespread than what I thought. – dmon May 04 '11 at 22:35
  • 3
    Ah, indeed it is. Take a look at the View's code (http://j.mp/kxQJIJ), in setBackgroundDrawable(). It's clearly overwriting the padding using the drawable's padding. But taking a look at the Drawable resource documentation, nothing supports padding except shapes, so that might be why it fails miserably. – dmon May 04 '11 at 22:42
  • And this is the getPadding() from the NinePatchDrawable: @Override public boolean getPadding(Rect padding) { padding.set(mPadding); **return true;** } – dmon May 04 '11 at 22:49
  • I see one of you filed a bug: http://code.google.com/p/android/issues/detail?id=17885 – Artem Russakovskii Aug 29 '11 at 22:40
  • 1
    And it seems Romain Guy just closed it as "works as intended". There's a comment in the code that justifies this behavior, so it was done on purpose. I guess it would just be nicer if it was documented somewhere. – dmon Sep 15 '11 at 05:01
  • 16
    This is definitely not "working as intended", when I mean to change only the background resource of an image and not any other properties by using setBackgroundResource() I would never have expected it to affect the padding of the element. – Elad Nava Aug 17 '12 at 21:15
  • Seems like this changed on 4.4 (maybe even in an earlier version). The padding is remembered when setting a background with `setBackground` or `setBackgroundResource` – Maik Jun 18 '14 at 16:52
  • 2
    Romain says "The reason why setting an image resets the padding is because 9-patch images can encode padding. If missing, we assume a padding of 0." It should be "If missing **&& is9Patch()**" imho. – adamdport Feb 24 '15 at 20:28
11

Adding onto what dmon has suggested, here is a function you can just throw in your util class so you don't have to jump through hoops every time you update a resource. This is really just his code wrapped in a function.

public static void updateBackgroundResourceWithRetainedPadding(View view, int resourceID)
{
    int bottom = view.getPaddingBottom();
    int top = view.getPaddingTop();
    int right = view.getPaddingRight();
    int left = view.getPaddingLeft();
    view.setBackgroundResource(resourceID);
    view.setPadding(left, top, right, bottom);
}
Brandon Romano
  • 1,003
  • 1
  • 12
  • 20
7

This is fixed in Lollipop, so

public static void setBackgroundResource(@NonNull View view, @DrawableRes int resId) {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        int paddingTop = view.getPaddingTop();
        int paddingLeft = view.getPaddingLeft();
        int paddingRight = view.getPaddingRight();
        int paddingBottom = view.getPaddingBottom();
        view.setBackgroundResource(resId);
        view.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    } else {
        view.setBackgroundResource(resId);
    }
}
e.shishkin
  • 1,155
  • 12
  • 9
6

Another solution that I opted in for instead of getting and setting padding in the code that dmon proposed is not using padding and instead using margins for inner elements.

Depending on your layout, it may actually be the same amount of XML code and wouldn't require any Java at all. It feels a little dirtier to me, but not as dirty as adding that Java code everywhere.

Artem Russakovskii
  • 20,170
  • 17
  • 87
  • 114
-2

In Monodroid, if I Post the call to SetBackgroundResource, then the top-padding and bottom-padding remains unaltered

private EditText _etInput

public void Disable()
{
    _etInput.Post(() => {
        _etInput.SetBackgroundResource(Resource.Drawable.input_field_background_disabled);
        _etInput.Clickable = false;
});

However, the left-padding gets reset to 0 !? If it's not posted, then all padding is reset to 0.

Thought this to be an interesting find worth posting about ...

samis
  • 5,746
  • 6
  • 29
  • 64