2010-12-02 25 views
100

Por ejemplo, el botón predeterminado tiene las siguientes dependencias entre sus estados y las imágenes de fondo:Cómo agregar un estado de botón personalizado

<?xml version="1.0" encoding="utf-8"?> 
<selector xmlns:android="http://schemas.android.com/apk/res/android"> 
    <item android:state_window_focused="false" android:state_enabled="true" 
     android:drawable="@drawable/btn_default_normal" /> 
    <item android:state_window_focused="false" android:state_enabled="false" 
     android:drawable="@drawable/btn_default_normal_disable" /> 
    <item android:state_pressed="true" 
     android:drawable="@drawable/btn_default_pressed" /> 
    <item android:state_focused="true" android:state_enabled="true" 
     android:drawable="@drawable/btn_default_selected" /> 
    <item android:state_enabled="true" 
     android:drawable="@drawable/btn_default_normal" /> 
    <item android:state_focused="true" 
     android:drawable="@drawable/btn_default_normal_disable_focused" /> 
    <item 
     android:drawable="@drawable/btn_default_normal_disable" /> 
</selector> 

¿Cómo puedo definir mi propio estado personalizado (Smth como android:state_custom), por lo que entonces Podría usarlo para cambiar dinámicamente la apariencia visual de mi botón?

+0

Necesitaba estados adicionales para una vista de Editar texto para determinar cuándo coinciden dos casillas de contraseña para mostrar una pequeña marca de verificación. – schwiz

Respuesta

222

La solución indicada por @ (Ted Hopp) funciona, pero necesita una pequeña corrección: en el selector, la los estados de los elementos necesitan un prefijo "aplicación:"; de lo contrario, el inflador no reconocerá correctamente el espacio de nombres y fallará en silencio; al menos esto es lo que me sucede.

Permítame Presentamos aquí la solución completa, con algunos detalles más:

En primer lugar, crear el archivo "res/valores/attrs.xml":

<?xml version="1.0" encoding="utf-8"?> 
<resources> 
    <declare-styleable name="food"> 
     <attr name="state_fried" format="boolean" /> 
     <attr name="state_baked" format="boolean" /> 
    </declare-styleable> 
</resources> 

a continuación, definir la clase personalizada. Por ejemplo, puede ser una clase "FoodButton", derivada de la clase "Button". Tendrás que implementar un constructor; aplicar éste, que parece ser la utilizada por el inflado:

public FoodButton(Context context, AttributeSet attrs) { 
    super(context, attrs); 
} 

En la parte superior de la clase derivada:

private static final int[] STATE_FRIED = {R.attr.state_fried}; 
private static final int[] STATE_BAKED = {R.attr.state_baked}; 

Además, sus variables de estado:

private boolean mIsFried = false; 
private boolean mIsBaked = false; 

Y un par de setters:

public void setFried(boolean isFried) {mIsFried = isFried;} 
public void setBaked(boolean isBaked) {mIsBaked = isBaked;} 

función Entonces anular "onCreateDrawableState":

@Override 
protected int[] onCreateDrawableState(int extraSpace) { 
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 2); 
    if (mIsFried) { 
     mergeDrawableStates(drawableState, STATE_FRIED); 
    } 
    if (mIsBaked) { 
     mergeDrawableStates(drawableState, STATE_BAKED); 
    } 
    return drawableState; 
} 

Por último, la más delicada pieza de este rompecabezas; el selector que define StateListDrawable que usará como fondo para su widget. Se trata de "res/estirable/food_button.xml" file:

<?xml version="1.0" encoding="utf-8"?> 
<selector xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:app="http://schemas.android.com/apk/res/com.mydomain.mypackage"> 
<item 
    app:state_baked="true" 
    app:state_fried="false" 
    android:drawable="@drawable/item_baked" /> 
<item 
    app:state_baked="false" 
    app:state_fried="true" 
    android:drawable="@drawable/item_fried" /> 
<item 
    app:state_baked="true" 
    app:state_fried="true" 
    android:drawable="@drawable/item_overcooked" /> 
<item 
    app:state_baked="false" 
    app:state_fried="false" 
    android:drawable="@drawable/item_raw" /> 
</selector> 

Aviso de la "app:" prefijo, mientras que con los estados estándar de Android que habría utilizado prefijo "androide". El espacio de nombres XML es crucial para una interpretación correcta por parte del inflador y depende del tipo de proyecto en el que está agregando atributos. Si se trata de una aplicación, reemplace com.mydomain.mypackage con el nombre del paquete real de su aplicación (nombre de la aplicación excluido). Si se trata de una biblioteca, debe utilizar "http://schemas.android.com/apk/res-auto" (y usar Tools R17 o posterior) o obtendrá errores en tiempo de ejecución.

Un par de notas:

  • Parece que no es necesario llamar a la función "refreshDrawableState", al menos, la solución funciona bien como es, en mi caso

  • Con el fin para usar su clase personalizada en un archivo xml de diseño, deberá especificar el nombre completo (ej. com.mydomain.mypackage.FoodButton)

  • Usted puede como weel mix-up estados convencionales (por ejemplo android: presionado, Android: habilitada, android: seleccionado) con los estados personalizados, con el fin de representar más complicadas combinaciones de estados

+2

Actualización: si la clase personalizada se deriva de TextView, en lugar de Button, la llamada a refreshDrawableState parece ser necesaria; de lo contrario, la apariencia del widget no se actualiza. La llamada se colocará en los incubadores. No he probado otras clases. Pruebas realizadas en un dispositivo froyo. –

+15

El 'refreshDrawableState' es definitivamente importante. No estoy absolutamente seguro de cuándo realmente se necesita. Pero en mi caso fue necesario al configurar el estado mediante programación. Supongo que posiblemente se llame desde la clase View automáticamente en onTouchEvent. Será mejor que lo agregue al método setSelected. – buergi

+1

GiorgioBarchiesi, tengo dos botones personalizados, y cuando trato de cambiar el estado de ambos botones del evento onClick de un botón, solo se modifica el botón, creo @buergi tiene razón al llamar al método refreshDrawableState. el Evento onClick. Gracias de nuevo por su maravilloso tutorial :) – Bolton

9

This thread muestra cómo agregar estados personalizados a botones y elementos similares. (Si no puede ver los nuevos grupos de Google en su navegador, hay una copia de la cadena here.)

+0

+1 muchas gracias, Ted! En este momento, el origen del problema se ha ido, así que no llegué a la implementación real. Sin embargo, si mi cliente vuelve a esto, intentaré la forma en que me lo indicó. –

+0

Se ve exactamente como lo que necesito, sin embargo, las listas de estados para mis estados personalizados no cambian. Me falta algo ... – schwiz

+0

¿Está llamando a refreshDrawableState()? –

5

Por favor, no olvide llamar refreshDrawableState dentro hilo de interfaz de usuario:

mHandler.post(new Runnable() { 
    @Override 
    public void run() { 
     refreshDrawableState(); 
    } 
}); 

se tomaron gran parte de mi tiempo para averiguar por qué mi botón no está cambiando su estado a pesar de todo se ve bien.

Cuestiones relacionadas