Android: клонирование чертежа для создания StateListDrawable с фильтрами

Я пытаюсь создать общую функцию фреймворка, которая выделяет любой объект Drawable при нажатии/фокусировке/выборе/и т. д..

Моя функция принимает Drawable и возвращает StateListDrawable, где состоянием по умолчанию является сам Drawable, а состояние для android.R.attr.state_pressed такое же, как и для Drawable, только с применением фильтра с использованием setColorFilter.

Моя проблема в том, что я не могу клонировать рисунок и сделать его отдельным экземпляром с примененным фильтром. Вот чего я пытаюсь достичь:

StateListDrawable makeHighlightable(Drawable drawable)
{
    StateListDrawable res = new StateListDrawable();

    Drawable clone = drawable.clone(); // how do I do this??

    clone.setColorFilter(0xFFFF0000, PorterDuff.Mode.MULTIPLY);
    res.addState(new int[] {android.R.attr.state_pressed}, clone);
    res.addState(new int[] { }, drawable);
    return res;
}

Если я не клонирую, то фильтр, очевидно, применяется к обоим состояниям. Пробовал играть с mutate(), не помогает..

Любые идеи?

Обновление:

Принятый ответ действительно клонирует рисунок. Это не помогло мне, потому что моя общая функция не работает по другой проблеме. Кажется, что когда вы добавляете drawable в StateList, он теряет все свои фильтры.


person talkol    schedule 02.11.2011    source источник
comment
Привет, вы нашли решение проблемы потери фильтров? Я столкнулся с той же проблемой :( В итоге я создал другое изображение из исходного изображения, клонировав растровое изображение и применив фильтр попиксельно. Да, это неэффективно, но у меня есть только куча небольших изображений, обработанных один раз.   -  person port443    schedule 15.11.2011
comment
Я не смог решить эту проблему с помощью StateListDrawable, но если вы не используете StateListDrawable и по-прежнему теряете свои фильтры, убедитесь, что ваши растровые изображения изменяемы. Есть хорошие связанные вопросы: stackoverflow.com/questions/5499637/, также я обнаружил, что LightingColorFilter работает в тех местах, где PorterDuff терпит неудачу... люблю этот андроид :)   -  person talkol    schedule 16.11.2011
comment
отличный ответ по этой ссылке " title="добавление цветового фильтра к рисунку изменяет все кнопки, использующие один и тот же рисунок"> stackoverflow.com/questions/10889415/   -  person Alan    schedule 24.01.2013
comment
Существует аналогичный побочный эффект, вызванный ImageView.setImageDrawable, который я смог обойти благодаря принятому ответу.   -  person Giulio Piancastelli    schedule 20.09.2013
comment
Я пытаюсь сделать то же самое, и это как-то работает, как и ожидалось, ColorFilter не потерялся ... Разница в том, что я изменил рисуемый объект.   -  person Henry    schedule 26.02.2018


Ответы (7)


Попробуйте следующее:

Drawable clone = drawable.getConstantState().newDrawable();
person Flavio    schedule 02.11.2011
comment
Спасибо! Этот метод, кажется, успешно клонирует рисунок. Функция, которую я пытался написать, не работает. Кажется, что когда объект рисования вставляется в StateList, он теряет свои фильтры :( - person talkol; 03.11.2011
comment
+1 за помощь в исправлении очень странной ошибки в MapView, когда повторное использование Drawable из ItemizedOverlay в AlertDialog приводило к перемещению ItemizedOverlay при срабатывании. Создание нового экземпляра Drawable устранило проблему. - person kskjon; 02.12.2011
comment
Чтобы работало правильно, если мы попробуем использовать метод setAlpha. В этом случае оба drawable меняют растровое изображение. Затем я получаю первый способ рисования как: getResources().getDrawable(), второй как: getResources().getDrawable().mutate(). - person Yura Shinkarev; 18.12.2012
comment
Большое спасибо, это решило проблему, с которой я столкнулся, когда я применил ограничивающую функцию из API Mapsforge. Теперь я могу успешно использовать чертежи везде! - person xarlymg89; 18.02.2013
comment
Теперь я могу глубоко клонировать BitmapDrawables и NinePatchDrawables СПАСИБО - person Diljeet; 13.05.2013
comment
@Flavio - я попробовал это с цветным фильтром, но он окрасил все экземпляры моего рисунка! Оказывается, вам нужно использовать .mutate() (см. мой ответ). - person Peter Ajtai; 31.08.2013
comment
Согласитесь с @PeterAjtai, он работает, но окрашивает все экземпляры моих рисунков... (см. ответ PeterAjtai) - person Vincent Ducastel; 16.04.2015
comment
У меня есть LayerDrawable, в котором я хотел изменить цветовой фильтр одного из его внутренних рисунков. Я пытался использовать это, но это не сработало. Я делаю это неправильно? - person android developer; 14.07.2016

Если вы примените фильтр /etc к чертежу, созданному с помощью getConstantState().newDrawable(), то все экземпляры этого чертежа также будут изменены, поскольку чертежи используют constantState в качестве кеша!

Итак, если вы раскрасите круг, используя цветовой фильтр и newDrawable(), вы измените цвет всех кругов.

Если вы хотите сделать это обновляемое рисование, не затрагивая другие экземпляры, тогда вы должны изменить это существующее постоянное состояние.

// To make a drawable use a separate constant state
drawable.mutate()

Для хорошего объяснения см.:

http://www.curious-creature.org/2009/05/02/drawable-mutations/

http://developer.android.com/reference/android/graphics/drawable/Drawable.html#mutate()

person Peter Ajtai    schedule 30.08.2013
comment
На самом деле mutate() возвращает точно такой же экземпляр, но его внутреннее состояние изменяется, поэтому применение цветового фильтра не повлияет на другие экземпляры. Можете ли вы просмотреть и исправить свой ответ? - person clemp6r; 01.10.2014
comment
@ clemp6r clemp6r, если вы не используете mutate для всех экземпляров изменения цвета - вам нужно вызвать mutate, чтобы изменить только цвет клона - person Peter Ajtai; 02.10.2014
comment
Проверьте ссылку на API (сделайте это mutable. — возвращает этот объект рисования) и исходный код (верните это). Вызов mutate() обязателен, но возвращаемый экземпляр тот же. Это не создает клон, это только изменяет внутреннее состояние экземпляра drawable, чтобы разрешить его изменение, не влияя на другие экземпляры того же drawable. - person clemp6r; 02.10.2014
comment
Ну, я не знаю насчет вопроса, но этот ответ делает именно то, что мне нужно... tU - person Evren Ozturk; 20.10.2014
comment
Я сталкиваюсь с этой самой проблемой только на Lollipop, но раньше она отлично работала даже на KitKat. Это очень странные проблемы, но спасибо за указатель на mutate() - person shalafi; 21.11.2014
comment
Это лучшие ссылки, которые вы дали для ознакомления - person Ashok Varma; 03.08.2015
comment
Отключает ли использование этого метода (мутация) кеширование рисунков с помощью getResources().getDrawable ? - person android developer; 14.07.2016

Это то, что работает для меня.

Drawable clone = drawable.getConstantState().newDrawable().mutate();
person Yanru Bi    schedule 24.03.2017
comment
ДА, я не знаю, ПОЧЕМУ, но только эта комбинация newDrawable() и mutate() работает для меня, любой другой одиночный mutate() или одиночный newDrawable() не работает для меня правильно - person Michał Ziobro; 06.11.2018
comment
Это рабочий ответ. - person WindRider; 29.04.2021

Это мое решение, основанное на этом SO-вопросе.

Идея состоит в том, что ImageView получает цветовой фильтр, когда пользователь прикасается к нему, и цветной фильтр удаляется, когда пользователь перестает его касаться. В памяти находится только 1 рисуемый/растровый рисунок, поэтому не нужно его тратить. Он работает как надо.

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));
person Malachiasz    schedule 28.01.2014
comment
У меня тоже работает! Интересное решение, спасибо!) P.S. андроид отстой, такой плохой не правильно работающий API :( - person Anton Kizema; 13.05.2015
comment
Я думаю, что это лучшее решение для устранения ошибок в (StateListDrawable + BitmapDrawable)! - person Xavier.S; 02.09.2016

Я ответил на связанный вопрос здесь

В основном кажется, что StateListDrawables действительно теряют свои фильтры. Я создал новый BitmapDrawale из измененной копии растрового изображения, которое изначально хотел использовать.

person Kuno    schedule 13.05.2012

Drawable clone = drawable.mutate().getConstantState().newDrawable().mutate();

в случае, если getConstantState() возвращает null.

person Martin Wang    schedule 06.12.2019

Получите возможность рисования клонов с помощью newDrawable(), но убедитесь, что он изменчив, иначе ваш эффект клонирования исчезнет, ​​я использовал эти несколько строк кода, и он работает, как и ожидалось. getConstantState() может быть нулевым, как это предлагается в аннотации, поэтому обрабатывайте это исключение RunTimeException при клонировании drawable.

Drawable.ConstantState state = d.mutate().getConstantState();
if (state != null) {
    Drawable drawable = state.newDrawable().mutate();
}
person Kishan Donga    schedule 22.08.2020