Android: clonagem de um drawable para tornar um StateListDrawable com filtros

90

Eu estou tentando fazer uma função quadro geral que faz qualquer Drawable tornar-se realçado quando pressionado / focalizado / selecionada / etc .

Minha função pega um Drawable e retorna um StateListDrawable, onde o estado padrão é o próprio Drawable, e o estado para android.R.attr.state_pressedé o mesmo drawable, apenas com um filtro aplicado usando setColorFilter.

Meu problema é que não consigo clonar o drawable e fazer uma instância separada dele com o filtro aplicado. Aqui está o que estou tentando alcançar:

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;
}

Se eu não clonar, o filtro será obviamente aplicado a ambos os estados. Tentei brincar mutate()mas não adiantou ..

Alguma ideia?

Atualizar:

A resposta aceita de fato clona um drawable. Mas isso não me ajudou porque minha função geral falha em um problema diferente. Parece que quando você adiciona um drawable a um StateList, ele perde todos os seus filtros.

talkol
fonte
Olá, você encontrou uma solução para drawables que perdem filtros? Encontrei o mesmo problema :( Acabei gerando outra imagem a partir da imagem de origem, clonando o bitmap e aplicando o filtro pixel a pixel. Sim, isso é ineficiente, mas tenho apenas um monte de pequenas imagens processadas uma vez.
port443
Não consegui resolver com StateListDrawable, mas se você não estiver usando StateListDrawable e ainda assim perder seus filtros, certifique-se de que seus bitmaps são mutáveis. Existem boas perguntas relacionadas: stackoverflow.com/questions/5499637/… , também descobri que o LightingColorFilter funciona em lugares onde o PorterDuff falha .. lovin this android :)
talkol
uma ótima resposta neste link stackoverflow.com/questions/10889415/…
Alan
Há um efeito colateral semelhante desencadeado por ImageView.setImageDrawable, que consegui contornar graças à resposta aceita.
Giulio Piancastelli de
Estou tentando fazer a mesma coisa e funciona como esperado de alguma forma, o ColorFilter não se perdeu ... A diferença é que mudei o drawable.
Henry

Respostas:

162

Experimente o seguinte:

Drawable clone = drawable.getConstantState().newDrawable();
Flavio
fonte
1
Obrigado! Este método parece clonar um drawable com sucesso. A função que eu estava tentando escrever não funciona. Parece que quando um drawable é inserido em uma StateList, ele perde seus filtros :(
talkol
3
+1 por me ajudar a corrigir um erro muito estranho no MapView onde reutilizar um Drawable do ItemizedOverlay em um AlertDialog fez o ItemizedOverlay se mover quando acionado. Criar uma nova instância do Drawable resolveu o problema.
kskjon
9
Faça para funcionar corretamente, se tentarmos usar o método setAlpha. Neste caso, ambos os bitmap de alteração de drawable. Então eu obtenho o primeiro drawable como: getResources (). GetDrawable (), o segundo como: getResources (). GetDrawable (). Mutate ().
Yura Shinkarev
Muito obrigado, ele corrigiu o problema que tive quando apliquei uma função delimitadora, da API Mapsforge. Agora posso usar os drawables em qualquer lugar!
xarlymg89
17
@Flavio - Eu tentei isso com um filtro de cor, mas coloriu todas as instâncias do meu drawable! Parece que você precisa usar .mutate()(veja minha resposta).
Peter Ajtai
104

Se você aplicar um filtro / etc a um drawable criado com, getConstantState().newDrawable()então todas as instâncias desse drawable também serão alteradas, já que os drawables usam o constantStatecomo cache!

Portanto, se você colorir um círculo usando um filtro de cores e um newDrawable(), você mudará a cor de todos os círculos.

Se você quiser tornar esse drawable atualizável sem afetar outras instâncias, deverá alterar esse estado constante existente.

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

Para uma boa explicação, consulte:

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

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

Peter Ajtai
fonte
Na verdade, mutate () retorna exatamente a mesma instância, mas seu estado interno é alterado, portanto, a aplicação de um filtro de cor não afetará as outras instâncias. Você pode revisar e corrigir sua resposta?
clemp6r 01 de
1
@ clemp6r se você não usar mutate todas as instâncias da mudança de cor - você precisa chamar mutate para mudar apenas a cor do clone
Peter Ajtai
2
Verifique a referência da API ("Tornar este drawable mutável. - Retorna este drawable") e o código-fonte ("retornar isso"). É necessário chamar mutate (), mas a instância retornada é a mesma. Isso não cria um clone, apenas altera o estado interno da instância do drawable para permitir modificá-lo sem impactar outras instâncias do mesmo drawable.
clemp6r 02 de
Bem, eu não sei sobre a pergunta, mas esta resposta faz exatamente o que eu precisava ... tU
Evren Ozturk
1
Esses são os melhores links, os quais você deu para referência
Ashok Varma
15

Isto é o que funciona para mim.

Drawable clone = drawable.getConstantState().newDrawable().mutate();
Yanru Bi
fonte
SIM, eu não sei POR QUE, mas apenas esta combinação newDrawable () e mutate () funciona para mim qualquer outro mutate único () ou newDrawable () único não funciona corretamente para mim
Michał Ziobro
12

Esta é a minha solução, com base nesta pergunta do SO .

A ideia é ImageViewobter um filtro de cor quando o usuário o tocar e o filtro de cor seja removido quando o usuário parar de tocá-lo. Apenas 1 drawable / bitmap está na memória, então não há necessidade de desperdiçá-lo. Funciona como deveria.

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;
    }
}

uso:

Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));
Malachiasz
fonte
Funciona para mim também! Essa é uma solução interessante, obrigado!) PS android é uma merda, tão ruim API que não funciona corretamente :(
Anton Kizema
Eu acho que esta é de longe a melhor solução para resolver bugs em (StateListDrawable + BitmapDrawable)!
Xavier.S
1

Eu respondi uma questão relacionada aqui

Basicamente, parece que StateListDrawables realmente perdem seus filtros. Criei um novo BitmapDrawale a partir de uma cópia alterada do Bitmap que originalmente queria usar.

Kuno
fonte
0
Drawable clone = drawable.mutate().getConstantState().newDrawable().mutate();

no caso de getConstantState()devoluções null.

Martin Wang
fonte
0

Obtenha o drawable clone usando, newDrawable()mas certifique-se de que é mutável, caso contrário, seu efeito de clone se foi, usei essas poucas linhas de código e está funcionando conforme o esperado. getConstantState()pode ser nulo, conforme sugerido pela anotação, portanto, manuseie esta RunTimeException ao clonar drawable.

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