Android: clonazione di un drawable per creare uno StateListDrawable con filtri


91

Sto cercando di creare una funzione framework generale che renda qualsiasi Drawable evidenziato quando viene premuto / focalizzato / selezionato / ecc .

La mia funzione accetta un Drawable e restituisce un StateListDrawable, dove lo stato predefinito è il Drawable stesso e lo stato per android.R.attr.state_pressedè lo stesso drawable, solo con un filtro applicato usando setColorFilter.

Il mio problema è che non posso clonare il drawable e crearne un'istanza separata con il filtro applicato. Ecco cosa sto cercando di ottenere:

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 non clono, il filtro viene ovviamente applicato a entrambi gli stati. Ho provato a giocarci mutate()ma non aiuta ..

Qualche idea?

Aggiornare:

La risposta accettata infatti clona un disegnabile. Non mi ha aiutato però perché la mia funzione generale fallisce su un problema diverso. Sembra che quando aggiungi un disegnabile a una StateList, perda tutti i suoi filtri.


Ciao, hai trovato una soluzione per i drawables che perdono i filtri? Ho riscontrato lo stesso problema :( ho finito per generare un'altra immagine dall'immagine sorgente clonando Bitmap e applicando il filtro pixel per pixel. Sì, questo è inefficiente, ma ho solo un mucchio di piccole immagini elaborate una volta.
port443

Non sono riuscito a risolverlo con StateListDrawable, ma se non stai usando StateListDrawable e stai ancora perdendo i filtri, assicurati che le tue bitmap siano modificabili. Ci sono buone domande correlate: stackoverflow.com/questions/5499637/… , inoltre ho scoperto che LightingColorFilter funziona in luoghi in cui PorterDuff fallisce .. adoro questo Android :)
talkol

un'ottima risposta su questo link stackoverflow.com/questions/10889415/…
Alan

C'è un effetto collaterale simile innescato da ImageView.setImageDrawable, che ho potuto aggirare grazie alla risposta accettata.
Giulio Piancastelli

Sto cercando di fare la stessa cosa e funziona come previsto in qualche modo, il ColorFilter non si è perso ... La differenza è che ho modificato il drawable.
Henry

Risposte:


162

Prova quanto segue:

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

1
Grazie! Questo metodo sembra clonare con successo un disegnabile. La funzione che stavo cercando di scrivere però non funziona .. Sembra che quando un disegnabile viene inserito in una StateList perda i suoi filtri :(
talkol

3
+1 per avermi aiutato a correggere un errore molto strano in MapView in cui il riutilizzo di un Drawable da ItemizedOverlay in un AlertDialog ha fatto spostare ItemizedOverlay quando attivato. La creazione di una nuova istanza del Drawable ha risolto il problema.
kskjon

9
Do per funzionare correttamente, se proviamo a utilizzare il metodo setAlpha. In questo caso entrambi i drawable cambiano bitmap. Quindi ottengo il primo drawable come: getResources (). GetDrawable (), il secondo come: getResources (). GetDrawable (). Mutate ().
Yura Shinkarev

Grazie mille, ha risolto il problema che avevo quando ho applicato una funzione di delimitazione, dall'API Mapsforge. Ora posso usare con successo i drawable ovunque!
xarlymg89

18
@Flavio - Ho provato questo con un filtro colorato, ma ha colorato tutte le istanze del mio disegnabile! Si scopre che sembra che tu debba usare .mutate()(vedi la mia risposta).
Peter Ajtai

106

Se applichi un filtro / etc a un drawable creato con, getConstantState().newDrawable()verranno modificate anche tutte le istanze di quel drawable, poiché i drawable usano constantStatecome cache!

Quindi, se colori un cerchio usando un filtro colorato e un newDrawable(), cambierai il colore di tutti i cerchi.

Se si desidera rendere aggiornabile questo disegnabile senza influire su altre istanze, è necessario modificare lo stato costante esistente.

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

Per una buona spiegazione vedere:

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

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


Infatti mutate () restituisce la stessa identica istanza, ma il suo stato interno viene modificato in modo che l'applicazione di un filtro colore non influisca su altre istanze. Puoi rivedere e correggere la tua risposta?
clemp6r

1
@ clemp6r se non usi mutate tutte le istanze del cambio di colore - devi chiamare mutate per cambiare solo il colore del clone
Peter Ajtai

2
Controllare il riferimento API ("Rendi modificabile questo disegnabile. - Restituisce questo oggetto disegnabile") e il codice sorgente ("restituisci questo"). È necessario chiamare mutate (), ma l'istanza restituita è la stessa. Questo non crea un clone, cambia solo lo stato interno dell'istanza disegnabile per consentirne la modifica senza influire su altre istanze dello stesso disegnabile.
clemp6r

Beh, non so della domanda, ma questa risposta fa esattamente la cosa di cui avevo bisogno ... tU
Evren Ozturk

1
Questi sono i link migliori, quelli che hai fornito come riferimento
Ashok Varma

15

Questo è ciò che funziona per me.

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

SÌ Non so PERCHÉ, ma solo questa combinazione newDrawable () e mutate () funziona per me qualsiasi altro singolo mutate () o singolo newDrawable () non funziona correttamente per me
Michał Ziobro

12

Questa è la mia soluzione, basata su questa domanda SO .

L'idea è che ImageViewottiene il filtro colore quando l'utente lo tocca e il filtro colore viene rimosso quando l'utente smette di toccarlo. Solo 1 drawable / bitmap è in memoria, quindi non c'è bisogno di sprecarlo. Funziona come dovrebbe.

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

utilizzo:

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

Funziona anche per me! Questa è una soluzione interessante, grazie!) PS Android fa schifo, API che non funziona correttamente :(
Anton Kizema

Penso che questa sia di gran lunga la migliore soluzione per risolvere i bug in (StateListDrawable + BitmapDrawable)!
Xavier.S

1

Ho risposto a una domanda correlata qui

Fondamentalmente sembra che StateListDrawables effettivamente perdano i loro filtri. Ho creato un nuovo BitmapDrawale da una copia modificata del Bitmap che inizialmente volevo usare.


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

in caso di getConstantState()ritorni null.


0

Ottieni il clone disegnabile usando newDrawable()ma assicurati che sia modificabile altrimenti il ​​tuo effetto clone sparisce, ho usato queste poche righe di codice e funziona come previsto. getConstantState()può essere nullo come suggerito dall'annotazione, quindi gestisci questa RunTimeException durante la clonazione di drawable.

Drawable.ConstantState state = d.mutate().getConstantState();
if (state != null) {
    Drawable drawable = state.newDrawable().mutate();
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.