StateListDrawable для переключения цветофильтров - PullRequest
10 голосов
/ 16 мая 2011

Я хочу создать пользовательские кнопки для использования в TabHost. Я пытался просто использовать один и тот же ресурс изображения (png), но менял цветовой фильтр в зависимости от состояния. Поэтому я сделал этот бит, чтобы он служил макетом пользовательской кнопки:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent" android:layout_height="fill_parent">
    <ImageView android:id="@+id/tab_icon"
        android:layout_centerInParent="true" android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView android:id="@+id/tab_text" android:layout_below="@id/tab_icon"
        android:layout_centerHorizontal="true" android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</RelativeLayout>

В своей деятельности я добавляю вкладки так:

tabHost.addTab(tabHost.newTabSpec(TAB_NAME_NEWS).setIndicator(buildTab(R.drawable.tab_icon_news, R.string.news))
          .setContent(newsIntent));

А это метод buildTab:

private final static int[] SELECTED = new int[] { android.R.attr.state_selected };
private final static int[] IDLE = new int[] { -android.R.attr.state_selected };

private View buildTab(int icon, int label) {
    LayoutInflater inflater = LayoutInflater.from(this);
    View view = inflater.inflate(R.layout.tab_button, null);
    StateListDrawable drawable = new StateListDrawable();

    Drawable selected = getResources().getDrawable(icon);
    selected.mutate();
    selected.setBounds(0, 0, selected.getIntrinsicWidth(), selected.getIntrinsicHeight());
    selected.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x0000FF00));
    drawable.addState(SELECTED, selected);

    Drawable idle = getResources().getDrawable(icon);
    idle.mutate();
    idle.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x000000FF));
    drawable.addState(IDLE, idle);

    ((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(drawable);
    ((TextView) view.findViewById(R.id.tab_text)).setText(getString(label));
    return view;
}

В выбранном состоянии изображение должно быть полностью зеленым (0x0000FF00), а в невыбранном состоянии оно должно быть синим (0x000000FF).

Проблема в том, что цветные фильтры, похоже, полностью игнорируются. Я не вижу изменения цвета ни при каких обстоятельствах.

Я также пытался получить тот же результат, задав свойство android:tint для <ImageView/>, но, очевидно, вы не можете использовать там ссылку на <selector>, так как он выдает NumberFormatException.

Я не вижу, что я делаю неправильно, поэтому любая помощь будет оценена.

Ответы [ 5 ]

15 голосов
/ 20 мая 2011

ОК, я никогда не заставлял работать вышеуказанный код, поэтому вот что я в итоге сделал.

Во-первых, я создал подкласс LayerDrawable:

public class StateDrawable extends LayerDrawable {

    public StateDrawable(Drawable[] layers) {
        super(layers);
    }

    @Override
    protected boolean onStateChange(int[] states) {
        for (int state : states) {
            if (state == android.R.attr.state_selected) {
                super.setColorFilter(Color.argb(255, 255, 195, 0), PorterDuff.Mode.SRC_ATOP);
            } else {
                super.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_ATOP);
            }
        }
        return super.onStateChange(states);
    }

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

}

Я изменил метод buildTab()к следующему:

private View buildTab(int icon, int label) {
    LayoutInflater inflater = LayoutInflater.from(this);
    View view = inflater.inflate(R.layout.tab_button, null);
    ((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(new StateDrawable(new Drawable[] { getResources()
          .getDrawable(icon) }));
    ((TextView) view.findViewById(R.id.tab_text)).setText(getString(label));
    return view;
}

Я все еще добавляю вкладки, как это:

Intent fooIntent = new Intent().setClass(this, FooActivity.class);
tabHost.addTab(tabHost.newTabSpec(TAB_NAME_INFO).setIndicator(buildTab(R.drawable.tab_icon_info, R.string.info)).setContent(infoIntent));

Это работает для меня, совместимо с Android 1.6.

10 голосов
/ 13 мая 2012

Не удалось решить проблему с помощью применения цветового фильтра непосредственно к рисованию.Для меня работало получить изображение как растровое изображение, создать пустое второе изображение с теми же показателями, определить холст для второго, применить этот цветовой фильтр к объекту рисования и нарисовать первое растровое изображение на втором.Наконец, создайте BitmapDrawable из нового Bitmap, и все готово.Вот код

    ImageButton imageButton = (ImageButton)findViewById(R.id.aga);

    Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.pen_circle);
    Bitmap oneCopy = Bitmap.createBitmap(one.getWidth(), one.getHeight(), Config.ARGB_8888);

    Canvas c = new Canvas(oneCopy);
    Paint p = new Paint();
    p.setColorFilter(new LightingColorFilter(Color.CYAN, 1));
    c.drawBitmap(one, 0, 0, p);

    StateListDrawable states = new StateListDrawable();
    states.addState(new int[] {android.R.attr.state_pressed}, new BitmapDrawable(oneCopy));
    states.addState(new int[] { }, imageButton.getDrawable());
    imageButton.setImageDrawable(states);
9 голосов
/ 01 октября 2013

Это мой класс, взломанный для поддержки ColorFilter:

Использование:

final Drawable icon = getResources().getDrawable(iconResId);
final Drawable filteredIcon = // this is important
        icon.getConstantState().newDrawable();
final FilterableStateListDrawable selectorDrawable =
        new FilterableStateListDrawable();
selectorDrawable.addState(ICON_STATE_SELECTED, filteredIcon,
        new PorterDuffColorFilter(mIconOverlayColor, PorterDuff.Mode.SRC_ATOP));
selectorDrawable.addState(ICON_STATE_DEFAULT, icon);

Как вы видите, ColorFilter не применяется непосредственно к рисованию, он ассоциируется с ним при добавлениисостояние селектора Drawable.

Здесь важно то, что

  • вам нужно создать новый Drawable из постоянного состояния, или вы измените постоянное состояние и, следовательно, любой экземпляриз этого рисования вокруг вашей деятельности.
  • вам нужно использовать мой пользовательский метод addState, он имеет то же имя, что и метод платформы addState, но я добавил дополнительный аргумент (ColorFilter).Этот метод НЕ существует в суперклассе фреймворка!

Код (грязный, но работает для меня):

/**
 * This is an extension to {@link android.graphics.drawable.StateListDrawable} that workaround a bug not allowing
 * to set a {@link android.graphics.ColorFilter} to the drawable in one of the states., it add a method
 * {@link #addState(int[], android.graphics.drawable.Drawable, android.graphics.ColorFilter)} for that purpose.
 */
public class FilterableStateListDrawable extends StateListDrawable {

    private int currIdx = -1;
    private int childrenCount = 0;
    private SparseArray<ColorFilter> filterMap;

    public FilterableStateListDrawable() {
        super();
        filterMap = new SparseArray<ColorFilter>();
    }

    @Override
    public void addState(int[] stateSet, Drawable drawable) {
        super.addState(stateSet, drawable);
        childrenCount++;
    }

    /**
     * Same as {@link #addState(int[], android.graphics.drawable.Drawable)}, but allow to set a colorFilter associated to this Drawable.
     *
     * @param stateSet    - An array of resource Ids to associate with the image.
     *                    Switch to this image by calling setState().
     * @param drawable    -The image to show.
     * @param colorFilter - The {@link android.graphics.ColorFilter} to apply to this state
     */
    public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) {
        // this is a new custom method, does not exist in parent class
        int currChild = childrenCount;
        addState(stateSet, drawable);
        filterMap.put(currChild, colorFilter);
    }

    @Override
    public boolean selectDrawable(int idx) {
        if (currIdx != idx) {
            setColorFilter(getColorFilterForIdx(idx));
        }
        boolean result = super.selectDrawable(idx);
        // check if the drawable has been actually changed to the one I expect
        if (getCurrent() != null) {
            currIdx = result ? idx : currIdx;
            if (!result) {
                // it has not been changed, meaning, back to previous filter
                setColorFilter(getColorFilterForIdx(currIdx));
            }
        } else if (getCurrent() == null) {
            currIdx = -1;
            setColorFilter(null);
        }
        return result;
    }

    private ColorFilter getColorFilterForIdx(int idx) {
        return filterMap != null ? filterMap.get(idx) : null;
    }
}

Я открыл ошибку по этому поводу: https://code.google.com/p/android/issues/detail?id=60183

ОБНОВЛЕНИЕ: ошибка была исправлена ​​в рамках, начиная с Lollipop, я думаю.Я думаю, что фиксация исправления такова: https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/

или на Github: https://github.com/android/platform_frameworks_base/commit/729427d451bc4d4d268335b8dc1ff6404bc1c91e

Мой обходной путь все еще должен работать после Lollipop, он просто не использует исправление от Google.

1 голос
/ 13 февраля 2015

Вот мой вариант кода @Malachiasz, он позволяет вам выбрать любую комбинацию состояний и цветов для применения к основному рисунку.

public class ColorFilteredStateDrawable extends StateListDrawable {

    private final int[][] states;
    private final int[] colors;

    public ColorFilteredStateDrawable(Drawable drawable, int[][] states,  int[] colors) {
        super();
        drawable.mutate();
        this.states = states;
        this.colors = colors;
        for (int i = 0; i < states.length; i++) {
            addState(states[i], drawable);
        }
    }

    @Override
    protected boolean onStateChange(int[] states) {
        if (this.states != null) {
            for (int i = 0; i < this.states.length; i++) {
                    if (StateSet.stateSetMatches(this.states[i], states)) {
                        super.setColorFilter(this.colors[i], PorterDuff.Mode.MULTIPLY);
                        return super.onStateChange(states);
                    }
            }
            super.clearColorFilter();
        }
        return super.onStateChange(states);
    }

    @Override
    public boolean isStateful() {
        return true;
    }
}
1 голос
/ 28 января 2014

Вот мой вариант кода Моппера.Идея состоит в том, что ImageView получает цветной фильтр, когда пользователь прикасается к нему, и цветной фильтр удаляется, когда пользователь перестаёт его касаться.

class PressedEffectStateListDrawable extends StateListDrawable {

    private int selectionColor;

    public PressedEffectStateListDrawable(Drawable drawable, int selectionColor) {
        super();
        this.selectionColor = selectionColor;
        addState(new int[] { android.R.attr.state_pressed }, drawable);
        addState(new int[] {}, drawable);
    }

    @Override
    protected boolean onStateChange(int[] states) {
        boolean isStatePressedInArray = false;
        for (int state : states) {
            if (state == android.R.attr.state_pressed) {
                isStatePressedInArray = true;
            }
        }
        if (isStatePressedInArray) {
            super.setColorFilter(selectionColor, PorterDuff.Mode.MULTIPLY);
        } else {
            super.clearColorFilter();
        }
        return super.onStateChange(states);
    }

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

использование:

Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));
...