dirkvranckaert
dirkvranckaert

Reputation: 1474

Ninepatch Drawable with ColorFilter

I'm create some calendar view and what I want to do is to create a background for a LineairLayout that is clickabe.

Therefore I create a StateListDrawable with two images:

  1. The image for the background
  2. The image when the item has been pressed

So far that works with this piece of code:

    NinePatchDrawable background = (NinePatchDrawable) context.getResources().getDrawable(R.drawable.calendar_item);
    Drawable backgroundFocus = context.getResources().getDrawable(R.drawable.calendar_focus);

    int stateFocused = android.R.attr.state_focused;
    int statePressed = android.R.attr.state_pressed;

    StateListDrawable sld = new StateListDrawable();
    sld.addState(new int[]{ stateFocused,  statePressed}, backgroundFocus);
    sld.addState(new int[]{-stateFocused,  statePressed}, backgroundFocus);
    sld.addState(new int[]{-stateFocused}, background);
    return sld;

But I would like to do something extra. I'dd like the user to be able to pass in a color that he wants to use to display the background. So the background var must be variable, but it must be based on the nine-patch drawable.

So I thought I could just do something like this:

background.setColorFilter(Color.RED, PorterDuff.Mode.DST_IN);

Where Color.RED must be replaced by the color of choice of the user.

But that doesn't seem to be working. The nine-patch is created perfectly but without the color fiilter being applied.

I also tried other PoterDuff.Mode 's:

If you have any clue what I'm doing wrong or what I could do else to solve my issue please let me know! :-)

Kr,

Dirk

Upvotes: 3

Views: 2093

Answers (1)

Vikram
Vikram

Reputation: 51571

I don't think you can assign ColorFilters for each Drawable in a StateListDrawable. Reason: The ColorFilter will be removed/replaced when the StateListDrawable changes state. To see this in action, change the order of the statements such that:

background.setColorFilter(Color.RED, PorterDuff.Mode.DST_IN);

comes after the creation of the StateListDrawable. You'll see that the ColorFilter IS applied. But, as soon as the state changes(click, then release), the ColorFilter isn't there any more.

StateListDrawables allow you to set a ColorFilter: StateListDrawable#setColorFilter(ColorFilter). This is how the supplied (or null) ColorFilter is used:

StateListDrawable#onStateChange(int[]):

@Override
protected boolean onStateChange(int[] stateSet) {
    ....
    if (selectDrawable(idx)) {    // DrawableContainer#selectDrawable(int)
        return true;
    }
    ....
}

DrawableContainer#selectDrawable(int):

public boolean selectDrawable(int idx) {
    ....
    if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) {
        Drawable d = mDrawableContainerState.mDrawables[idx];
        mCurrDrawable = d;
        mCurIndex = idx;

        if (d != null) {
            ....

            // So, at this stage, any ColorFilter you might have supplied
            // to `d` will be replaced by the ColorFilter you
            // supplied to the StateListDrawable, or `null`
            // if you didn't supply any.                 
            d.setColorFilter(mColorFilter);

            ....
        }
    } else {
        ....
    }
}

Workaround:

If at all a possible, use an ImageView (match_parent for dimensions) for visual communication. Set the StateListDrawable that you've created as the ImageView's background. Create another StateListDrawable for the overlay:

StateListDrawable sldOverlay = new StateListDrawable();

// Match Colors with states (and ultimately, Drawables)
sldOverlay.addState(new int[] { statePressed }, 
                         new ColorDrawable(Color.TRANSPARENT));

sldOverlay.addState(new int[] { -statePressed }, 
                         new ColorDrawable(Color.parseColor("#50000000")));

// Drawable that you already have
iv1.setBackground(sld);

// Drawable that you just created
iv1.setImageDrawable(sldOverlay);

Another possibility: use a FrameLayout in place of LinearLayout. LinearLayouts do not have a foreground property.

// StateListDrawable
frameLayout.setBackground(sld);

// For tint
frameLayout.setForeground(sldOverlay);

It does involve overdraw, making it a sub-optimal solution/workaround. Perhaps you can look at extending StateListDrawable and DrawableContainer. And since you are not using a ColorFilter for the StateListDrawable, you can remove d.setColorFilter(mColorFilter); from overridden DrawableContainer#selectDrawable(int).

Upvotes: 2

Related Questions