StateListDrawable pour changer les filtres de couleur

Je veux créer des boutons personnalisés à utiliser dans un TabHost. Je n'ai pas essayé d'utiliser la même ressource d'image (png), mais le changement de filtre de couleur dépend de l'état. J'ai donc fait ce bit pour servir de mise en page pour le bouton personnalisé:

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <ImageView android:id="@+id/tab_icon" android:layout_centerInParent="true" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/tab_text" android:layout_below="@id/tab_icon" android:layout_centerHorizontal="true" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> 

Dans mon activité, j'ajoute les onglets comme ceci:

  • Passer des données entre les activités dans Android
  • Comment écrire du style dans le texte d'erreur de EditText dans Android?
  • La nouvelle visibilité du mot de passe permet-elle de basculer Brokenable Drawable existant pour EditTexts?
  • SpeechRecognizer sur un appareil Android sans Google Apps
  • Impossible de construire la notification: constructeur illégal
  • Comment utiliser la réflexion pour changer le service de sauvegarde?
  •  tabHost.addTab(tabHost.newTabSpec(TAB_NAME_NEWS).setIndicator(buildTab(R.drawable.tab_icon_news, R.string.news)) .setContent(newsIntent)); 

    Et c'est la méthode 'buildTab':

     private final static int[] SELECTED = new int[] { android.R.attr.state_selected }; private final static int[] IDLE = new int[] { -android.R.attr.state_selected }; private View buildTab(int icon, int label) { LayoutInflater inflater = LayoutInflater.from(this); View view = inflater.inflate(R.layout.tab_button, null); StateListDrawable drawable = new StateListDrawable(); Drawable selected = getResources().getDrawable(icon); selected.mutate(); selected.setBounds(0, 0, selected.getIntrinsicWidth(), selected.getIntrinsicHeight()); selected.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x0000FF00)); drawable.addState(SELECTED, selected); Drawable idle = getResources().getDrawable(icon); idle.mutate(); idle.setColorFilter(new LightingColorFilter(0xFFFFFFFF, 0x000000FF)); drawable.addState(IDLE, idle); ((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(drawable); ((TextView) view.findViewById(R.id.tab_text)).setText(getString(label)); return view; } 

    Dans l'état sélectionné, l'image doit être complètement verte ( 0x0000FF00 ) et, dans l'état non sélectionné, elle doit être bleue ( 0x000000FF ).

    Le problème est que les filtres de couleur semblent être totalement ignorés. Je ne peux pas voir les couleurs changer en aucune circonstance.

    J'ai également essayé d'obtenir le même résultat en configurant la propriété android:tint sur <ImageView/> , mais apparemment, vous ne pouvez pas utiliser une référence à un <selector> là-bas, puisqu'il lance une NumberFormatException .

    Je ne vois pas ce que je fais mal alors toute aide serait appréciée.

  • Petite fenêtre contextuelle pour les instructions, comme celles de Foursquare
  • Dagger 2 sur Android, aucun message d'erreur
  • Android n'a pas pu trouver cette application dans le PackageManager io.crash.air
  • Fade in Activity de l'activité précédente dans Android
  • DP5 7.0 - L'ajout d'extras à une intention en attente échoue-t-il?
  • Comment accéder à la liste de rejet automatique intégrée
  • 5 Solutions collect form web for “StateListDrawable pour changer les filtres de couleur”

    OK, je n'ai jamais eu le code ci-dessus pour fonctionner, alors voici ce que j'ai fini de faire.

    D'abord, j'ai classé LayerDrawable en sous-classe:

     public class StateDrawable extends LayerDrawable { public StateDrawable(Drawable[] layers) { super(layers); } @Override protected boolean onStateChange(int[] states) { for (int state : states) { if (state == android.R.attr.state_selected) { super.setColorFilter(Color.argb(255, 255, 195, 0), PorterDuff.Mode.SRC_ATOP); } else { super.setColorFilter(Color.GRAY, PorterDuff.Mode.SRC_ATOP); } } return super.onStateChange(states); } @Override public boolean isStateful() { return true; } } 

    J'ai changé la méthode buildTab() de ce qui suit:

     private View buildTab(int icon, int label) { LayoutInflater inflater = LayoutInflater.from(this); View view = inflater.inflate(R.layout.tab_button, null); ((ImageView) view.findViewById(R.id.tab_icon)).setImageDrawable(new StateDrawable(new Drawable[] { getResources() .getDrawable(icon) })); ((TextView) view.findViewById(R.id.tab_text)).setText(getString(label)); return view; } 

    J'ajoute encore les onglets comme ceci:

     Intent fooIntent = new Intent().setClass(this, FooActivity.class); tabHost.addTab(tabHost.newTabSpec(TAB_NAME_INFO).setIndicator(buildTab(R.drawable.tab_icon_info, R.string.info)).setContent(infoIntent)); 

    Cela fonctionne pour moi, compatible avec Android 1.6.

    Impossible de le résoudre avec l'application d'un filtre couleur directement sur le drawable non plus. Ce qui a fonctionné pour moi, c'est obtenir l'image en tant que Bitmap, créer une seconde vide avec les mêmes mesures, définir une toile pour la seconde, appliquer ce filtre de couleur à un objet de peinture et dessiner le premier bitmap sur le second. Enfin, créez un BitmapDrawable à partir du nouveau Bitmap et vous avez terminé. Voici le code

      ImageButton imageButton = (ImageButton)findViewById(R.id.aga); Bitmap one = BitmapFactory.decodeResource(getResources(), R.drawable.pen_circle); Bitmap oneCopy = Bitmap.createBitmap(one.getWidth(), one.getHeight(), Config.ARGB_8888); Canvas c = new Canvas(oneCopy); Paint p = new Paint(); p.setColorFilter(new LightingColorFilter(Color.CYAN, 1)); c.drawBitmap(one, 0, 0, p); StateListDrawable states = new StateListDrawable(); states.addState(new int[] {android.R.attr.state_pressed}, new BitmapDrawable(oneCopy)); states.addState(new int[] { }, imageButton.getDrawable()); imageButton.setImageDrawable(states); 

    C'est ma classe, piratée pour supporter ColorFilter:

    Usage:

     final Drawable icon = getResources().getDrawable(iconResId); final Drawable filteredIcon = // this is important icon.getConstantState().newDrawable(); final FilterableStateListDrawable selectorDrawable = new FilterableStateListDrawable(); selectorDrawable.addState(ICON_STATE_SELECTED, filteredIcon, new PorterDuffColorFilter(mIconOverlayColor, PorterDuff.Mode.SRC_ATOP)); selectorDrawable.addState(ICON_STATE_DEFAULT, icon); 

    Comme vous voyez, le ColorFilter n'est pas appliqué directement sur le drawable, il l'associe en ajoutant un état au sélecteur Drawable.

    Ce qui importe ici, c'est que

    • Vous devez créer un nouveau dessinable à partir de l'état constant ou vous modifiez l'état constant et, par conséquent, toute instance pouvant être dessinée autour de votre activité.
    • Vous devez utiliser ma méthode addState personnalisée, elle a le même nom de la méthode framework addState, mais j'ai ajouté un argument supplémentaire (ColorFilter). Cette méthode n'existe PAS dans la superclasse de cadre!

    Le code (sale, mais fonctionne pour moi):

     /** * This is an extension to {@link android.graphics.drawable.StateListDrawable} that workaround a bug not allowing * to set a {@link android.graphics.ColorFilter} to the drawable in one of the states., it add a method * {@link #addState(int[], android.graphics.drawable.Drawable, android.graphics.ColorFilter)} for that purpose. */ public class FilterableStateListDrawable extends StateListDrawable { private int currIdx = -1; private int childrenCount = 0; private SparseArray<ColorFilter> filterMap; public FilterableStateListDrawable() { super(); filterMap = new SparseArray<ColorFilter>(); } @Override public void addState(int[] stateSet, Drawable drawable) { super.addState(stateSet, drawable); childrenCount++; } /** * Same as {@link #addState(int[], android.graphics.drawable.Drawable)}, but allow to set a colorFilter associated to this Drawable. * * @param stateSet - An array of resource Ids to associate with the image. * Switch to this image by calling setState(). * @param drawable -The image to show. * @param colorFilter - The {@link android.graphics.ColorFilter} to apply to this state */ public void addState(int[] stateSet, Drawable drawable, ColorFilter colorFilter) { // this is a new custom method, does not exist in parent class int currChild = childrenCount; addState(stateSet, drawable); filterMap.put(currChild, colorFilter); } @Override public boolean selectDrawable(int idx) { if (currIdx != idx) { setColorFilter(getColorFilterForIdx(idx)); } boolean result = super.selectDrawable(idx); // check if the drawable has been actually changed to the one I expect if (getCurrent() != null) { currIdx = result ? idx : currIdx; if (!result) { // it has not been changed, meaning, back to previous filter setColorFilter(getColorFilterForIdx(currIdx)); } } else if (getCurrent() == null) { currIdx = -1; setColorFilter(null); } return result; } private ColorFilter getColorFilterForIdx(int idx) { return filterMap != null ? filterMap.get(idx) : null; } } 

    J'ai ouvert un bug à ce sujet: https://code.google.com/p/android/issues/detail?id=60183

    MISE À JOUR: le bug a été corrigé dans le cadre, puisque Lollipop je pense. Je pense que le commit est celui-ci: https://android.googlesource.com/platform/frameworks/base/+/729427d%5E!/

    Ou sur Github: https://github.com/android/platform_frameworks_base/commit/729427d451bc4d4d268335b8dc1ff6404bc1c91e

    Ma solution de contournement devrait toujours fonctionner après Lollipop, elle n'utilise pas la solution par Google.

    Voici ma variation du code de Mopper. L'idée est que ImageView obtient un filtre de couleur lorsque l'utilisateur le touche et le filtre de couleur est supprimé lorsque l'utilisateur cesse de le toucher.

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

    Voici ma variation du code @Malachiasz, cela vous permet de choisir n'importe quelle combinaison d'états et de couleurs à appliquer à la base dessinable.

     public class ColorFilteredStateDrawable extends StateListDrawable { private final int[][] states; private final int[] colors; public ColorFilteredStateDrawable(Drawable drawable, int[][] states, int[] colors) { super(); drawable.mutate(); this.states = states; this.colors = colors; for (int i = 0; i < states.length; i++) { addState(states[i], drawable); } } @Override protected boolean onStateChange(int[] states) { if (this.states != null) { for (int i = 0; i < this.states.length; i++) { if (StateSet.stateSetMatches(this.states[i], states)) { super.setColorFilter(this.colors[i], PorterDuff.Mode.MULTIPLY); return super.onStateChange(states); } } super.clearColorFilter(); } return super.onStateChange(states); } @Override public boolean isStateful() { return true; } } 
    coAndroid est un fan Android de Google, tout sur les téléphones Android, Android Wear, Android Dev et Android Games Apps.