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.


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();

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 ()


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();

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

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.


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

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


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();
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.