Max Nagl

StateListColorFilteredDrawable

posted in Utils on February 23, 2016

StateListColorFilteredDrawable lets you assign a number of ColorFilters to a single Drawable and swap them according to states.

Usage

First you have to create the Drawable you want to filter. Then wrap it in a StateListColorFilteredDrawable and call addState to set the ColorFilter for a specific state.

// Create a drawable directly
Drawable original = ...;

// Wrap the drawable
StateListColorFilteredDrawable colored = new StateListColorFilteredDrawable(original);

// Add states
colored.addState(new int[]{android.R.attr.state_pressed}, Color.RED, PorterDuff.Mode.MULTIPLY);
colored.addState(StateSet.WILD_CARD, Color.GREEN, PorterDuff.Mode.MULTIPLY);

// User the colored drawable
view.setBackgroundDrawable(colored);

If you inflated your view from an XML-File and want to modify the background you can also use the static injectInBackground() method.

// Directly inject the StateListColorFilteredDrawable into the background of a view.
StateListColorFilteredDrawable colored = StateListColorFilteredDrawable.injectInBackground(view);

// Add states
colored.addState(new int[]{android.R.attr.state_pressed}, Color.RED, PorterDuff.Mode.MULTIPLY);
colored.addState(StateSet.WILD_CARD, Color.GREEN, PorterDuff.Mode.MULTIPLY);

// No need to call view.setBackgroundDrawable(colored);

The Class

/*
 * Copyright 2016 Max Nagl
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package na.gl.util;

import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.StateSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * Lets you assign a number of ColorFilters to a single Drawable and swap them according
 * to states.
 */
public class StateColorFilteredDrawable extends LayerDrawable {
    private final List<ColorFilterState> states = new ArrayList<>();

    private class ColorFilterState {
        int[] state;
        ColorFilter colorFilter;
    }

    /**
     * Creates a new layer drawable with the list of specified layers that changes
     * colorFilters according to states.
     *
     * @param layers a list of drawables to use as layers in this new drawable,
     *               must be non-null
     */
    public StateColorFilteredDrawable(@NonNull Drawable[] layers) {
        super(layers);
    }

    /**
     * Creates a new layer drawable with the list of specified layers that changes
     * colorFilters according to states.
     *
     * @param layer a drawable to use as only layers in this new drawable,
     *               must be non-null
     */
    public StateColorFilteredDrawable(@NonNull Drawable layer) {
        this(new Drawable[] {layer});
    }

    /**
     * Wraps the background of a view with a StateColorFilteredDrawable.
     * @param view The view with the background to wrap.
     * @return The wrapped background.
     */
    @SuppressWarnings("deprecation")
    public static @Nullable
    StateColorFilteredDrawable injectInBackground(@NonNull View view) {
        Drawable original = view.getBackground();
        if (original == null) return null;
        StateColorFilteredDrawable colored = new StateColorFilteredDrawable(original);
        view.setBackgroundDrawable(colored);
        return colored;
    }

    /**
     * Removes all states and ColorFilters.
     */
    public void clearStates() {
        states.clear();
    }

    /**
     * Convenience for {@link #addState(int[], ColorFilter)} which constructs PorterDuffColorFilter.
     */
    public void addState(int[] stateSet, int color, PorterDuff.Mode mode) {
        addState(stateSet, new PorterDuffColorFilter(color, mode));

    }

    /**
     * Add a ColorFilter for a specific state.
     * @param stateSet - An array of resource Ids to associate with the image.
     *                 Switch to this image by calling setState().
     * @param colorFilter - The ColorFilter to apply in this state.
     */
    public void addState(int[] stateSet, @Nullable ColorFilter colorFilter) {
        ColorFilterState cfs = new ColorFilterState();
        cfs.colorFilter = colorFilter;
        cfs.state = stateSet;
        states.add(cfs);
    }

    @Override
    protected boolean onStateChange(int[] states) {
        boolean stateFound = false;
        for (ColorFilterState state : this.states) {
            if (StateSet.stateSetMatches(state.state, states)) {
                super.setColorFilter(state.colorFilter);
                stateFound = true;
                break;
            }
        }
        if (!stateFound) {
            super.setColorFilter(null);
        }
        super.onStateChange(states);
        invalidateSelf();
        return true;
    }

    @Override
    public boolean isStateful() {
        return true;
    }
}