Android: cloner un drawable pour créer un StateListDrawable avec des filtres

91

Je suis en train de faire une fonction cadre général qui fait tout Drawable est mis en surbrillance lorsque pressé / concentré / sélectionné / etc .

Ma fonction prend un Drawable et retourne un StateListDrawable, où l'état par défaut est le Drawable lui-même, et l'état pour android.R.attr.state_pressedest le même drawable, juste avec un filtre appliqué à l'aide de setColorFilter.

Mon problème est que je ne peux pas cloner le dessinable et en créer une instance séparée avec le filtre appliqué. Voici ce que j'essaye de réaliser:

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

Si je ne clone pas, le filtre est évidemment appliqué aux deux états. J'ai essayé de jouer avec mutate()mais ça n'aide pas.

Des idées?

Mettre à jour:

La réponse acceptée clone en effet un dessinable. Cela ne m'a pas aidé car ma fonction générale échoue sur un problème différent. Il semble que lorsque vous ajoutez un dessinable à une StateList, il perd tous ses filtres.

talkol
la source
Salut, avez-vous trouvé une solution pour que les drawables perdent des filtres? J'ai rencontré le même problème: (J'ai fini par générer une autre image à partir de l'image source en clonant Bitmap et en appliquant un filtre pixel par pixel. Oui, c'est inefficace, mais je n'ai qu'un tas de petites images traitées une fois.
port443
Je n'ai pas pu le résoudre avec StateListDrawable, mais si vous n'utilisez pas StateListDrawable et que vous perdez toujours vos filtres, assurez-vous que vos bitmaps sont modifiables. Il y a de bonnes questions connexes: stackoverflow.com/questions/5499637/… , aussi j'ai découvert que LightingColorFilter fonctionne dans les endroits où PorterDuff échoue .. aime cet androïde :)
talkol
une excellente réponse sur ce lien stackoverflow.com/questions/10889415/…
Alan
Il y a un effet secondaire similaire déclenché par ImageView.setImageDrawable, que j'ai pu contourner grâce à la réponse acceptée.
Giulio Piancastelli
J'essaye de faire la même chose et ça marche comme prévu, le ColorFilter ne s'est pas perdu ... La différence est que j'ai fait muter le dessinable.
Henry

Réponses:

162

Essayez ce qui suit:

Drawable clone = drawable.getConstantState().newDrawable();
Flavio
la source
1
Merci! Cette méthode semble cloner un dessinable avec succès. La fonction que j'essayais d'écrire ne fonctionne pas. Il semble que lorsqu'un dessinable est inséré dans une StateList, il perd ses filtres :(
talkol
3
+1 pour m'aider à corriger une erreur très étrange dans MapView où la réutilisation d'un Drawable de ItemizedOverlay dans un AlertDialog a fait bouger ItemizedOverlay lorsqu'il était déclenché. La création d'une nouvelle instance du Drawable a résolu le problème.
kskjon
9
Faites pour fonctionner correctement, si nous essayons d'utiliser la méthode setAlpha. Dans ce cas, les deux bitmap peuvent être dessinés. Ensuite, je reçois le premier dessinable en tant que: getResources (). GetDrawable (), puis en tant que: getResources (). GetDrawable (). Mutate ().
Yura Shinkarev
Merci beaucoup, cela a résolu le problème que j'avais lorsque j'ai appliqué une fonction de délimitation, à partir de l'API Mapsforge. Maintenant, je peux utiliser avec succès les drawables partout!
xarlymg89
18
@Flavio - J'ai essayé cela avec un filtre de couleur, mais il a coloré toutes les instances de mon dessin! Il s'avère que vous devez utiliser .mutate()(voir ma réponse).
Peter Ajtai
106

Si vous appliquez un filtre / etc à un dessinable créé avec getConstantState().newDrawable()alors toutes les instances de ce dessinable seront également modifiées, puisque les dessinables utilisent le constantStatecomme cache!

Donc, si vous colorez un cercle en utilisant un filtre de couleur et un newDrawable(), vous changerez la couleur de tous les cercles.

Si vous souhaitez que ce dessin puisse être mis à jour sans affecter les autres instances, vous devez alors faire muter cet état constant existant.

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

Pour une bonne explication, voir:

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

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

Peter Ajtai
la source
En fait, mutate () renvoie exactement la même instance, mais son état interne est modifié, donc l'application d'un filtre de couleur n'aura pas d'impact sur les autres instances. Pouvez-vous revoir et corriger votre réponse?
clemp6r
1
@ clemp6r si vous n'utilisez pas mutate toutes les instances du changement de couleur - vous devez appeler mutate pour ne changer que la couleur du clone
Peter Ajtai
2
Vérifiez la référence de l' API ("Make this drawable mutable. - Returns this drawable") et le code source ("return this"). L'appel de mutate () est obligatoire, mais l'instance renvoyée est la même. Cela ne crée pas de clone, cela change uniquement l'état interne de l'instance dessinable pour permettre de la modifier sans affecter les autres instances du même dessinable.
clemp6r
Eh bien, je ne sais pas pour la question, mais cette réponse fait exactement ce dont j'avais besoin ... tU
Evren Ozturk
1
Ce sont les meilleurs liens, ceux que vous avez donnés pour référence
Ashok Varma
15

C'est ce qui fonctionne pour moi.

Drawable clone = drawable.getConstantState().newDrawable().mutate();
Yanru Bi
la source
OUI je ne sais pas POURQUOI mais seule cette combinaison newDrawable () et mutate () fonctionne pour moi, tout autre simple mutate () ou single newDrawable () ne fonctionne pas correctement pour moi
Michał Ziobro
12

C'est ma solution, basée sur cette question SO .

L'idée est d' ImageViewobtenir un filtre de couleur lorsque l'utilisateur le touche, et le filtre de couleur est supprimé lorsque l'utilisateur cesse de le toucher. Un seul dessinable / bitmap est en mémoire, donc pas besoin de le gaspiller. Cela fonctionne comme il se doit.

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

usage:

Drawable drawable = new FastBitmapDrawable(bm);
imageView.setImageDrawable(new PressedEffectStateListDrawable(drawable, 0xFF33b5e5));
Malachiasz
la source
Fonctionne aussi pour moi! C'est une solution intéressante, merci!) PS android est nul, tellement mauvais API ne fonctionne pas correctement :(
Anton Kizema
Je pense que c'est de loin la meilleure solution pour résoudre les bugs dans (StateListDrawable + BitmapDrawable)!
Xavier.S
1

J'ai répondu à une question connexe ici

Fondamentalement, il semble que StateListDrawables perd effectivement ses filtres. J'ai créé un nouveau BitmapDrawale à partir d'une copie modifiée du Bitmap que je voulais utiliser à l'origine.

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

en cas de getConstantState()retour null.

Martin Wang
la source
0

Obtenez le clonage en utilisant newDrawable()mais assurez-vous qu'il est mutable sinon votre effet de clone a disparu, j'ai utilisé ces quelques lignes de code et cela fonctionne comme prévu. getConstantState()peut être nul comme suggéré par l'annotation, alors gérez cette RunTimeException pendant que vous clonez un dessinable.

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