Android: Klonen eines Drawable, um ein StateListDrawable mit Filtern zu erstellen

90

Ich versuche , einen allgemeinen Rahmen Funktion zu machen , die jede Drawable macht hervorgehoben werden , wenn sie gedrückt / fokussiert / ausgewählt / etc .

Meine Funktion nimmt ein Drawable und gibt ein StateListDrawable zurück, wobei der Standardstatus das Drawable selbst ist und der Status für android.R.attr.state_presseddasselbe Drawable ist, nur mit einem Filter, der mit angewendet wird setColorFilter.

Mein Problem ist, dass ich das Drawable nicht klonen und mit dem angewendeten Filter eine separate Instanz davon erstellen kann. Folgendes versuche ich zu erreichen:

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

Wenn ich nicht klone, wird der Filter offensichtlich auf beide Zustände angewendet. Ich habe versucht, damit zu spielen, mutate()aber es hilft nicht.

Irgendwelche Ideen?

Aktualisieren:

Die akzeptierte Antwort klont tatsächlich ein Drawable. Es hat mir jedoch nicht geholfen, da meine allgemeine Funktion bei einem anderen Problem fehlschlägt. Es scheint, dass beim Hinzufügen eines Zeichens zu einer StateList alle Filter verloren gehen.

talkol
quelle
Hallo, haben Sie eine Lösung gefunden, um Drawables Filter zu verlieren? Ich bin auf dasselbe Problem gestoßen :( Ich habe letztendlich ein anderes Bild aus dem
Quellbild generiert,
Ich konnte es nicht mit StateListDrawable lösen, aber wenn Sie StateListDrawable nicht verwenden und trotzdem Ihre Filter verlieren, stellen Sie sicher, dass Ihre Bitmaps veränderbar sind. Es gibt gute verwandte Fragen: stackoverflow.com/questions/5499637/… , außerdem habe ich festgestellt, dass LightingColorFilter an Orten funktioniert, an denen PorterDuff versagt. Ich liebe diesen Android :)
talkol
eine gute Antwort auf diesen Link stackoverflow.com/questions/10889415/…
Alan
Es gibt einen ähnlichen Nebeneffekt, der ausgelöst wurde ImageView.setImageDrawableund den ich dank der akzeptierten Antwort umgehen konnte.
Giulio Piancastelli
Ich versuche das Gleiche zu tun und es funktioniert wie erwartet, der ColorFilter ist nicht verloren gegangen ... Der Unterschied ist, dass ich das Drawable mutiert habe.
Henry

Antworten:

162

Versuche Folgendes:

Drawable clone = drawable.getConstantState().newDrawable();
Flavio
quelle
1
Vielen Dank! Diese Methode scheint ein Drawable erfolgreich zu klonen. Die Funktion, die ich zu schreiben versuchte, funktioniert jedoch nicht. Es scheint, dass ein Drawable, wenn es in eine StateList eingefügt wird, seine Filter verliert :(
talkol
3
+1 für die Behebung eines sehr seltsamen Fehlers in MapView, bei dem bei erneuter Verwendung eines Drawable aus dem ItemizedOverlay in einem AlertDialog das ItemizedOverlay beim Auslösen verschoben wurde. Das Erstellen einer neuen Instanz von Drawable hat das Problem behoben.
kskjon
9
Tun Sie, um richtig zu arbeiten, wenn wir versuchen, die setAlpha-Methode zu verwenden. In diesem Fall ändern beide zeichnbare Bitmap. Dann bekomme ich erstens als: getResources (). GetDrawable (), zweitens als: getResources (). GetDrawable (). Mutate ().
Yura Shinkarev
Vielen Dank, es hat das Problem behoben, das ich hatte, als ich eine Begrenzungsfunktion aus der API Mapsforge angewendet habe. Jetzt kann ich die Drawables überall erfolgreich einsetzen!
Xarlymg89
17
@Flavio - Ich habe es mit einem Farbfilter versucht, aber es hat alle Instanzen meines Zeichens gefärbt! Es stellt sich heraus, dass es so aussieht, als müssten Sie es verwenden .mutate()(siehe meine Antwort).
Peter Ajtai
104

Wenn Sie einen Filter / etc auf ein mit erstelltes Drawable getConstantState().newDrawable()anwenden, werden auch alle Instanzen dieses Drawables geändert, da Drawables das constantStateals Cache verwenden!

Wenn Sie also einen Kreis mit einem Farbfilter und a färben newDrawable(), ändern Sie die Farbe aller Kreise.

Wenn Sie diese zeichnbare Datei aktualisierbar machen möchten, ohne andere Instanzen zu beeinflussen, müssen Sie diesen vorhandenen konstanten Status ändern.

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

Für eine gute Erklärung siehe:

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

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

Peter Ajtai
quelle
Tatsächlich gibt mutate () genau dieselbe Instanz zurück, aber sein interner Status wird geändert, sodass die Anwendung eines Farbfilters keine Auswirkungen auf andere Instanzen hat. Können Sie Ihre Antwort überprüfen und korrigieren?
Clemp6r
1
@ clemp6r Wenn Sie nicht alle Instanzen der Farbänderung mutieren verwenden - Sie müssen mutieren aufrufen, um nur die Farbe des Klons zu ändern
Peter Ajtai
2
Überprüfen Sie die API- Referenz ("Make this drawable veränderbar. - Gibt dieses Drawable zurück") und den Quellcode ("return this"). Der Aufruf von mutate () ist erforderlich, die zurückgegebene Instanz ist jedoch dieselbe. Dadurch wird kein Klon erstellt. Dadurch wird nur der interne Status der zeichnbaren Instanz geändert, um eine Änderung zu ermöglichen, ohne dass dies Auswirkungen auf andere Instanzen derselben zeichnbaren Instanz hat.
Clemp6r
Nun, ich weiß nichts über die Frage, aber diese Antwort macht genau das, was ich brauchte ... tU
Evren Ozturk
1
Das sind die besten Links, die Sie als Referenz angegeben haben
Ashok Varma
14

Das funktioniert bei mir.

Drawable clone = drawable.getConstantState().newDrawable().mutate();
Yanru Bi
quelle
JA Ich weiß nicht WARUM, aber nur diese Kombination newDrawable () und mutate () funktioniert für mich. Jede andere einzelne mutate () oder einzelne newDrawable () funktioniert für mich nicht richtig
Michał Ziobro
12

Dies ist meine Lösung, basierend auf dieser SO-Frage .

Die Idee ist, dass ImageViewder Farbfilter abgerufen wird, wenn der Benutzer ihn berührt, und der Farbfilter entfernt wird, wenn der Benutzer ihn nicht mehr berührt. Es befindet sich nur 1 Zeichen- / Bitmap im Speicher, sodass Sie diese nicht verschwenden müssen. Es funktioniert wie es sollte.

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

Verwendung:

Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));
Malachiasz
quelle
Funktioniert auch bei mir! Das ist eine interessante Lösung, danke!) PS Android saugt, so schlecht funktioniert nicht richtig API :(
Anton Kizema
Ich denke, dies ist bei weitem die beste Lösung, um Fehler in (StateListDrawable + BitmapDrawable) zu beheben!
Xavier.S
1

Ich antwortete eine ähnliche Frage hier

Grundsätzlich scheint es, als ob StateListDrawables tatsächlich ihre Filter verlieren. Ich habe eine neue BitmapDrawale aus einer geänderten Kopie der Bitmap erstellt, die ich ursprünglich verwenden wollte.

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

falls getConstantState()zurückkommt null.

Martin Wang
quelle