Cómo: definir un elemento de tema (estilo) para un widget personalizado

86

Escribí un widget personalizado para un control que usamos ampliamente en toda nuestra aplicación. La clase de widget se deriva ImageButtony amplía de un par de formas sencillas. Definí un estilo que puedo aplicar al widget a medida que se usa, pero prefiero configurarlo a través de un tema. En R.styleableveo atributos de estilo de widget como imageButtonStyley textViewStyle. ¿Hay alguna forma de crear algo así para el widget personalizado que escribí?

jsmith
fuente

Respuestas:

209

Sí, hay una forma:

Suponga que tiene una declaración de atributos para su widget (en attrs.xml):

<declare-styleable name="CustomImageButton">
    <attr name="customAttr" format="string"/>
</declare-styleable>

Declare un atributo que usará para una referencia de estilo (en attrs.xml):

<declare-styleable name="CustomTheme">
    <attr name="customImageButtonStyle" format="reference"/>
</declare-styleable>

Declare un conjunto de valores de atributos predeterminados para el widget (en styles.xml):

<style name="Widget.ImageButton.Custom" parent="android:style/Widget.ImageButton">
    <item name="customAttr">some value</item>
</style>

Declare un tema personalizado (en themes.xml):

<style name="Theme.Custom" parent="@android:style/Theme">
    <item name="customImageButtonStyle">@style/Widget.ImageButton.Custom</item>
</style>

Utilice este atributo como tercer argumento en el constructor de su widget (en CustomImageButton.java):

public class CustomImageButton extends ImageButton {
    private String customAttr;

    public CustomImageButton( Context context ) {
        this( context, null );
    }

    public CustomImageButton( Context context, AttributeSet attrs ) {
        this( context, attrs, R.attr.customImageButtonStyle );
    }

    public CustomImageButton( Context context, AttributeSet attrs,
            int defStyle ) {
        super( context, attrs, defStyle );

        final TypedArray array = context.obtainStyledAttributes( attrs,
            R.styleable.CustomImageButton, defStyle,
            R.style.Widget_ImageButton_Custom ); // see below
        this.customAttr =
            array.getString( R.styleable.CustomImageButton_customAttr, "" );
        array.recycle();
    }
}

Ahora tienes que aplicar Theme.Customa todas las actividades que usan CustomImageButton(en AndroidManifest.xml):

<activity android:name=".MyActivity" android:theme="@style/Theme.Custom"/>

Eso es todo. Ahora CustomImageButtonintenta cargar valores de customImageButtonStyleatributo predeterminados del atributo del tema actual. Si no se encuentra tal atributo en el tema o el valor del atributo es , se utilizará @nullel argumento final para obtainStyledAttributes: Widget.ImageButton.Customen este caso.

Puede cambiar los nombres de todas las instancias y todos los archivos (excepto AndroidManifest.xml), pero sería mejor usar la convención de nomenclatura de Android.

Miguel
fuente
4
¿Hay alguna manera de hacer exactamente lo mismo sin usar un tema? Tengo algunos widgets personalizados, pero quiero que los usuarios puedan usar estos widgets con cualquier tema que deseen. Hacerlo de la forma en que publicaste requeriría que los usuarios usen mi tema.
theDazzler
3
@theDazzler: si te refieres a una biblioteca que los desarrolladores pueden usar, entonces podrán crear sus propios temas y especificar el estilo para el widget usando un atributo de estilo en el tema ( customImageButtonStyleen esta respuesta). Así es como se personaliza la barra de acciones en Android. Y si te refieres a cambiar un tema en tiempo de ejecución, entonces no es posible con este enfoque.
Michael
@ Michael, sí, me refiero a una biblioteca que los desarrolladores pueden usar. Tu comentario resolvió mi problema. ¡Gracias!
theDazzler
30
Lo más sorprendente de todo esto es que alguien en Google piensa que esto está bien.
Glenn Maynard
1
¿ name="CustomTheme"El declare-styleableenvoltorio de atributo tiene algún propósito? Es solo para organización, porque no veo que se use en ningún lado. ¿Puedo poner todos mis atributos de estilo para varios widgets en un contenedor?
Tenfour04
4

Otro aspecto además de la excelente respuesta de Michael es anular los atributos personalizados en los temas. Suponga que tiene varias vistas personalizadas que hacen referencia al atributo personalizado "fondo_personalizado".

<declare-styleable name="MyCustomStylables">
    <attr name="custom_background" format="color"/>
</declare-styleable>

En un tema, defines cuál es el valor

<style name="MyColorfulTheme" parent="AppTheme">
    <item name="custom_background">#ff0000</item>
</style>

o

<style name="MyBoringTheme" parent="AppTheme">
    <item name="custom_background">#ffffff</item>
</style>

Puede hacer referencia al atributo en un estilo

<style name="MyDefaultLabelStyle" parent="AppTheme">
    <item name="android:background">?background_label</item>
</style>

Observe el signo de interrogación, que también se usa para el atributo de Android de referencia como en

?android:attr/colorBackground

Como la mayoría de ustedes ha notado, puede -y probablemente debería- usar referencias de @color en lugar de colores codificados.

Entonces, ¿por qué no hacerlo?

<item name="android:background">@color/my_background_color</item>

No puede cambiar la definición de "my_background_color" en tiempo de ejecución, mientras que puede cambiar de tema fácilmente.

mir
fuente