Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected LayerDrawable behavior when drawing layers containing InsetDrawable's

I am trying to build a LayerDrawable in xml where upper layers will occasionally completely obscure lower layers. To make the lower layers smaller, I am using an InsetDrawable to wrap another drawable to make it smaller than the full size of the view. I find unexpectedly, however, that any layers placed on top of the layer containing the inset also has the inset applied to it. I can't find documentation supporting this behavior, and am confused why this would be the case.

In the example below, I make a LayerDrawable with 3 layers. The bottom and top layers contain oval shape drawables that are meant to take up the entire view. The middle layer is a rectangle drawable inside of an InsetDrawable. The code is below:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item>
        <shape android:shape="oval" >
            <solid android:color="#00ff00" />
        </shape>
    </item>
    <item>
        <inset
            android:insetBottom="4dp"
            android:insetLeft="4dp"
            android:insetRight="4dp"
            android:insetTop="4dp" >
            <shape android:shape="rectangle" >
                <solid android:color="#ff0000" />
            </shape>
        </inset>
    </item>
    <item>
        <shape android:shape="oval" >
            <solid android:color="#0000ff" />
        </shape>
    </item>

</layer-list>

Calling setBackgroundDrawable(getResources().getDrawable(drawableId)); in my view produces a green oval that fills the entire view as expected, with a red rectangle inset 4dp as expected, but the blue oval on the top layer is also inset 4dp and drawn completely within the bounds of the red rectangle.

I would expect the blue oval to completely obscure the green oval and most of the red rectangle, but instead it is inset inside the red rectangle. Is there any way to make the blue circle fill the view yet keep it on top?

like image 857
happydude Avatar asked Dec 21 '22 02:12

happydude


2 Answers

I also don't see where it is documented, but padding in a LayerDrawable is cumulative. That is, padding at one layer affects the bounds of all higher layers. This is from the source for LayerDrawable:

@Override
protected void onBoundsChange(Rect bounds) {
    final ChildDrawable[] array = mLayerState.mChildren;
    final int N = mLayerState.mNum;
    int padL=0, padT=0, padR=0, padB=0;
    for (int i=0; i<N; i++) {
        final ChildDrawable r = array[i];
        r.mDrawable.setBounds(bounds.left + r.mInsetL + padL,
                              bounds.top + r.mInsetT + padT,
                              bounds.right - r.mInsetR - padR,
                              bounds.bottom - r.mInsetB - padB);
        padL += mPaddingL[i];
        padR += mPaddingR[i];
        padT += mPaddingT[i];
        padB += mPaddingB[i];
    }
}

(LayerDrawable.getPadding(Rect) follows the same logic.) Since an InsetDrawable uses its insets as padding (as documented), this explains the behavior you're seeing.

I think this is a poor design decision, but you're kind of stuck with it, I'm afraid. I don't think it can be overridden.

like image 94
Ted Hopp Avatar answered Apr 06 '23 01:04

Ted Hopp


Ted's answer is the best answer, but I'll share this workaround that helped me. I was specifically having padding problems with a TextView, so I made a custom TextView instead which ignores the background drawable's padding.

public class HackTextView extends TextView {

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

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

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

    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void setBackground(Drawable background) {
        super.setBackground(hackDrawable(background));
    }

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

    private Drawable hackDrawable(Drawable background){
        return new LayerDrawable(new Drawable[]{background}){
            @Override
            public boolean getPadding(Rect padding) {
                padding.set(0, 0, 0, 0);
                return false;
            }
        };
    }

}
like image 36
Dave Morgan Avatar answered Apr 05 '23 23:04

Dave Morgan