6

Stavo usando il metodo qui sotto per mascherare correttamente i drawable composti con android.support.design 23.0.1. Ora che hanno rilasciato 23.1.0 non funziona più su api LVL16, tutti i miei drawable sono neri.Android AppCompat 23.1.0 Tint Compound Drawable

Qualcuno ha un suggerimento?

private void setCompoundColor(TextView view) { 
    Drawable drawable = view.getCompoundDrawables()[0]; 
    Drawable wrap = DrawableCompat.wrap(drawable); 
    DrawableCompat.setTint(wrap, ContextCompat.getColor(this, R.color.primaryLighter2)); 
    DrawableCompat.setTintMode(wrap, PorterDuff.Mode.SRC_IN); 
    wrap = wrap.mutate(); 
    view.setCompoundDrawablesRelativeWithIntrinsicBounds(wrap, null, null, null); 
    } 

Grazie.

+1

controllare [questa risposta] (http://stackoverflow.com/a/35867517/2826147) per l'aggiornamento. –

+0

Il codice di Philippe David funziona, ma dalla mia esperienza dovresti scrivere 'wrap = wrap.mutate();' prima di 'DrawableCompat.setTint()'. Altrimenti non funzionerà correttamente poiché il drawable originale verrà modificato. – marius

risposta

8

Ho affrontato lo stesso problema la scorsa settimana e in AppCompatTextView v23.1.0 risulta che i drawable composti sono automaticamente colorati.

Ecco la soluzione che ho trovato, con più spiegazioni sul motivo per cui ho fatto questo di seguito. Non è molto pulito ma almeno ti permette di colorare i tuoi drawable composti!

SOLUZIONE

inserire questo codice in una classe di supporto o nel vostro TextView custom/Button:

/** 
* The app compat text view automatically sets the compound drawable tints for a static array of drawables ids. 
* If the drawable id is not in the list, the lib apply a null tint, removing the custom tint set before. 
* There is no way to change this (private attributes/classes, only set in the constructor...) 
* 
* @param object the object on which to disable default tinting. 
*/ 
public static void removeDefaultTinting(Object object) { 
    try { 
     // Get the text helper field. 
     Field mTextHelperField = object.getClass().getSuperclass().getDeclaredField("mTextHelper"); 
     mTextHelperField.setAccessible(true); 
     // Get the text helper object instance. 
     final Object mTextHelper = mTextHelperField.get(object); 
     if (mTextHelper != null) { 
      // Apply tint to all private attributes. See AppCompat source code for usage of theses attributes. 
      setObjectFieldToNull(mTextHelper, "mDrawableStartTint"); 
      setObjectFieldToNull(mTextHelper, "mDrawableEndTint"); 
      setObjectFieldToNull(mTextHelper, "mDrawableLeftTint"); 
      setObjectFieldToNull(mTextHelper, "mDrawableTopTint"); 
      setObjectFieldToNull(mTextHelper, "mDrawableRightTint"); 
      setObjectFieldToNull(mTextHelper, "mDrawableBottomTint"); 
     } 
    } catch (NoSuchFieldException e) { 
     // If it doesn't work, we can do nothing else. The icons will be white, we will see it. 
     e.printStackTrace(); 
    } catch (IllegalAccessException e) { 
     // If it doesn't work, we can do nothing else. The icons will be white, we will see it. 
     e.printStackTrace(); 
    } 
} 

/** 
* Set the field of an object to null. 
* 
* @param object the TextHelper object (class is not accessible...). 
* @param fieldName the name of the tint field. 
*/ 
private static void setObjectFieldToNull(Object object, String fieldName) { 
    try { 
     Field tintField; 
     // Try to get field from class or super class (depends on the implementation). 
     try { 
      tintField = object.getClass().getDeclaredField(fieldName); 
     } catch (NoSuchFieldException e) { 
      tintField = object.getClass().getSuperclass().getDeclaredField(fieldName); 
     } 
     tintField.setAccessible(true); 
     tintField.set(object, null); 

    } catch (NoSuchFieldException e) { 
     // If it doesn't work, we can do nothing else. The icons will be white, we will see it. 
     e.printStackTrace(); 
    } catch (IllegalAccessException e) { 
     // If it doesn't work, we can do nothing else. The icons will be white, we will see it. 
     e.printStackTrace(); 
    } 
} 

quindi è possibile chiamare removeDefaultTinting(this); su ogni costruzione della classe di estendere AppCompatTextView o AppCompatButton. Ad esempio:

public MyCustomTextView(Context context, AttributeSet attrs, int defStyle) { 
    super(context, attrs, defStyle); 
    removeDefaultTinting(this); 
} 

Con questo, il codice che funziona con v23.0.1 dovrebbe funzionare su v23.1.0.

Non sono soddisfatto dall'uso del reflection per modificare gli attributi nella lib di AppCompat, ma questo è l'unico modo in cui ho trovato la colorazione sui drawable composti con v23.1.0. Si spera che qualcuno troverà una soluzione migliore, o una colorazione combinabile composta verrà aggiunta ai metodi pubblici AppCompat.

UPDATE

ho trovato un'altra soluzione più semplice: questo bug si verifica solo se si imposta drawable composti usando XML. Non impostarli in xml, quindi impostarli nel codice e funzionerà. Il codice errato nel costruttore, che imposta i drawable dopo che è stato chiamato non è influenzato.

spiegazioni

nel costruttore AppCompatTextView, un aiutante testo viene inizializzato:

mTextHelper.loadFromAttributes(attrs, defStyleAttr); 
mTextHelper.applyCompoundDrawablesTints(); 

Nella funzione TextHelper loadFromAttributes, un elenco tinta viene creato per ogni drawable composto. Come puoi vedere, mDrawableXXXTint.mHasTintList è sempre impostato su true. mDrawableXXXTint.mTintList è il colore della tinta che verrà applicato e viene ottenuto solo dai valori hardcoded di AppCompat. Per i tuoi drawable personalizzati, sarà sempre nullo. Quindi si finisce con una tinta che ha un "elenco tinta" nullo.

TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0); 
    final int ap = a.getResourceId(0, -1); 

    // Now read the compound drawable and grab any tints 
    if (a.hasValue(1)) { 
     mDrawableLeftTint = new TintInfo(); 
     mDrawableLeftTint.mHasTintList = true; 
     mDrawableLeftTint.mTintList = tintManager.getTintList(a.getResourceId(1, 0)); 
    } 
    if (a.hasValue(2)) { 
     mDrawableTopTint = new TintInfo(); 
     mDrawableTopTint.mHasTintList = true; 
     mDrawableTopTint.mTintList = tintManager.getTintList(a.getResourceId(2, 0)); 
    } 

... 

Il problema è che questa tinta è applicata nel costruttore, e ogni volta che un disegnabile è impostato o modificato:

@Override 
protected void drawableStateChanged() { 
    super.drawableStateChanged(); 
    if (mBackgroundTintHelper != null) { 
     mBackgroundTintHelper.applySupportBackgroundTint(); 
    } 
    if (mTextHelper != null) { 
     mTextHelper.applyCompoundDrawablesTints(); 
    } 
} 

Quindi, se si applica una tinta a un drawable composto, e quindi chiamare un super metodo come view.setCompoundDrawablesRelativeWithIntrinsicBounds, l'helper di testo applicherà la sua tinta nulla al tuo drawable, rimuovendo tutto ciò che hai fatto ...

Infine, ecco la funzione di applicare la tinta:

final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) { 
    if (drawable != null && info != null) { 
     TintManager.tintDrawable(drawable, info, mView.getDrawableState()); 
    } 
} 

Il TintInfo parametri è l'attributo mDrawableXXXTint della classe texthelper. Come puoi vedere, se è nullo, non viene applicata alcuna tinta. L'impostazione di tutti gli attributi di tinta drawable su null impedisce ad AppCompat di applicare la tinta e consente di eseguire le operazioni desiderate con i drawable.

Non ho trovato un modo pulito per bloccare questo comportamento o per ottenere il colore desiderato. Tutti gli attributi sono privati, senza getter.

+0

Wow. Non il tipo di risposta che pensavo di ottenere! Proveremo la tua soluzione, ma questo tipo di soluzione è un peccato per Android ... pensi che sarebbe saggio aprire un bug per Google da verificare? Grazie mille :) –

+3

Prego! Ho trascorso mezza giornata usando il debugger per capire perché i miei drawable erano bianchi, quindi quando ho visto la tua domanda mi sono sentito obbligato a creare un account e pubblicare quello che ho trovato :) Probabilmente è una buona idea aprire un bug, ho appena fatto prendi il tempo Questa è una soluzione temporanea che probabilmente smetterà di funzionare la prossima volta che modificherà AppCompat. Il codice che usano per colorare i drawable composti non è troppo lungo o complicato, è semplicemente completamente inaccessibile da una classe esterna. Un semplice setter per la sfumatura di ciascun composto drawable, ed è fisso ... –

+1

Fatto per il problema. Da ieri, ho scoperto che questa lib rompe anche TransitionDrawable su qualche dispositivo :) https://code.google.com/p/android/issues/detail?id=191111 –