Declarar atributos de estilo en Android

81

Existe muy poca documentación sobre la declare-styleableetiqueta mediante la cual podemos declarar estilos personalizados para componentes. Encontré esta lista de valores válidos para el formatatributo de la attretiqueta. Si bien eso es bueno en la medida de lo posible, no explica cómo usar algunos de esos valores. Navegando attr.xml (la fuente de Android para los atributos estándar), descubrí que puedes hacer cosas como:

<!-- The most prominent text color.  -->
<attr name="textColorPrimary" format="reference|color" />

El formatatributo evidentemente se puede ajustar a una combinación de valores. Presumiblemente, el formatatributo ayuda al analizador a interpretar un valor de estilo real. Luego descubrí esto en attr.xml:

<!-- Default text typeface. -->
<attr name="typeface">
    <enum name="normal" value="0" />
    <enum name="sans" value="1" />
    <enum name="serif" value="2" />
    <enum name="monospace" value="3" />
</attr>

<!-- Default text typeface style. -->
<attr name="textStyle">
    <flag name="normal" value="0" />
    <flag name="bold" value="1" />
    <flag name="italic" value="2" />
</attr>

Ambos parecen declarar un conjunto de valores permitidos para el estilo indicado.

Entonces tengo dos preguntas:

  1. ¿Cuál es la diferencia entre un atributo de estilo que puede tomar uno de un conjunto de enumvalores y uno que puede tomar un conjunto de flagvalores?
  2. ¿Alguien sabe de alguna documentación mejor sobre cómo declare-styleablefunciona (aparte de la ingeniería inversa del código fuente de Android)?
Ted Hopp
fuente

Respuestas:

72

Aquí está esta pregunta: definir atributos personalizados con algo de información, pero no mucha.

Y esta publicación . Tiene buena información sobre banderas y enumeraciones:

Banderas de atributos XML personalizados

Las banderas son tipos de atributos especiales en el sentido de que solo se les permite un subconjunto muy pequeño de valores, es decir, aquellos que se definen debajo de la etiqueta de atributo. Las banderas se especifican mediante un atributo "nombre" y un atributo "valor". Se requiere que los nombres sean únicos dentro de ese tipo de atributo, pero los valores no necesitan serlo. Esta es la razón por la que durante la evolución de la plataforma Android tuvimos "fill_parent" y "match_parent", ambos mapeados con el mismo comportamiento. Sus valores eran idénticos.

El atributo de nombre se asigna al nombre utilizado en el lugar del valor dentro del XML de diseño y no requiere un prefijo de espacio de nombres. Por lo tanto, para el "tilingMode" anterior, elegí "centro" como valor de atributo. Podría haber elegido con la misma facilidad "estirar" o "repetir", pero nada más. Ni siquiera se habría permitido sustituir los valores reales.

El atributo de valor debe ser un número entero. La elección de la representación numérica estándar o hexadecimal depende de usted. Hay algunos lugares dentro del código de Android donde se usan ambos y el compilador de Android está feliz de aceptarlos.

Enumeraciones de atributos XML personalizados

Las enumeraciones se usan de una manera casi idéntica a las banderas con una disposición, pueden usarse indistintamente con números enteros. Bajo el capó, Enums y Integers se asignan al mismo tipo de datos, es decir, un Integer. Al aparecer en la definición de atributo con Integers, las enumeraciones sirven para evitar “números mágicos” que siempre son malos. Es por eso que puede tener un "android: layout_width" con una dimensión, un número entero o una cadena denominada "fill_parent".

Para poner esto en contexto, supongamos que creo un atributo personalizado llamado "layout_scroll_height" que acepta un número entero o una cadena "scroll_to_top". Para hacerlo, agregaría un atributo de formato "entero" y lo seguiría con la enumeración:

<attr name="layout_scroll_height" format="integer">  
    <enum name="scroll_to_top" value="-1"/> 
</attr>

La única estipulación al usar Enums de esta manera es que un desarrollador que use su Vista personalizada podría colocar a propósito el valor "-1" en los parámetros de diseño. Esto activaría la lógica de caso especial de "scroll_to_top". Este comportamiento inesperado (o esperado) podría relegar rápidamente su biblioteca a la pila de "código heredado" si los valores de Enum se eligieron mal.


En mi opinión, los valores reales que puede agregar en realidad a un atributo están limitados por lo que puede obtener de él. Consulte la AttributeSetreferencia de la clase aquí para obtener más sugerencias.

Puede obtener:

  • booleanos ( getAttributeBooleanValue),
  • flotadores ( getAttributeFloatValue),
  • ints ( getAttributeIntValue),
  • ints (como getAttributeUnsignedIntValue),
  • y cuerdas ( getAttributeValue)
Aleadam
fuente
Gracias por esos enlaces. El blog de tipo estadístico es particularmente agradable. Está lo suficientemente cerca de la "documentación real" que estoy marcando esto como resuelto.
Ted Hopp
@Ted de hecho, esa publicación tiene gran información. De todos modos, a menos que esté escribiendo una biblioteca de vistas reutilizables o ampliando el marco, no me preocuparía por estas enumeraciones. Bools, floats, ints y strings pueden llevarlo lejos para vistas personalizadas regulares. Eso es, por supuesto, si la pregunta no era solo para satisfacer una curiosidad muy sana :)
Aleadam
@Aleadam - La pregunta fue motivada por una aplicación real. He estado usando atributos personalizados y necesitaba agregar uno nuevo. Resulta que el formato correcto para el nuevo atributo es una enumeración, pero hasta que leí el enlace que proporcionó, no pude encontrar ninguna información sobre la diferencia entre usar enumy flag.
Ted Hopp
@Ted Me alegro de que haya sido útil. Hay algunas otras publicaciones en ese blog que parecen interesantes, aunque solo las hojeé, no tuve tiempo de leerlas todavía.
Aleadam
3
+1 para cualquiera que vincule mi blog. Realmente intenté analizar para qué se usaba cada una de esas etiquetas. Puede que no esté completo y quizás debería actualizarse a medida que pasa el tiempo, pero si alguien tiene algo que agregar, soy todo oídos.
Wheaties
70

La respuesta de @Aleadam es muy útil, pero en mi humilde opinión, omite una diferencia importante entre enumy flag. El primero está destinado a que elijamos uno, y solo un valor cuando asignamos el atributo correspondiente para alguna Vista. Sin embargo, los valores de este último se pueden combinar utilizando el operador OR bit a bit.

Un ejemplo, en res/values/attr.xml

<!-- declare myenum attribute -->
<attr name="myenum">
    <enum name="zero" value="0" />
    <enum name="one" value="1" />
    <enum name="two" value="2" />
    <enum name="three" value="3" />
</attr>

<!-- declare myflags attribute -->
<attr name="myflags">
    <flag name="one" value="1" />
    <flag name="two" value="2" />
    <flag name="four" value="4" />
    <flag name="eight" value="8" />
</attr>

<!-- declare our custom widget to be styleable by these attributes -->
<declare-styleable name="com.example.MyWidget">
    <attr name="myenum" />
    <attr name="myflags" />
</declare-styleable>

En res/layout/mylayout.xmlahora podemos hacer

<com.example.MyWidget
    myenum="two"
    myflags="one|two"
    ... />

Entonces, una enumeración selecciona uno de sus valores posibles, mientras que las banderas se pueden combinar. Los valores numéricos deben reflejar esta diferencia, por lo general, querrá que la secuencia vaya 0,1,2,3,...para enumeraciones (para usarse como índices de matriz, por ejemplo) y banderas para 1,2,4,8,...que se puedan agregar o eliminar de forma independiente, usando OR bit |a bit para combinar banderas.

Podríamos definir explícitamente "metabanderas" con valores que no sean una potencia de 2, y así introducir una especie de abreviatura de combinaciones comunes. Por ejemplo, si hubiéramos incluido esto en nuestra myflagsdeclaración

<flag name="three" value="3" />

entonces podríamos haber escrito myflags="three"en lugar de myflags="one|two", para obtener resultados completamente idénticos a 3 == 1|2.

Personalmente, me gusta incluir siempre

<flag name="none" value="0" /> <!-- or "normal, "regular", and so on -->
<flag name="all" value="15" /> <!-- 15 == 1|2|4|8 -->

lo que me permitirá desarmar o poner todas las banderas a la vez.

Más sutilmente, podría darse el caso de que una bandera esté implícita en otra. Entonces, en nuestro ejemplo, suponga que la eightbandera que se está configurando debería forzar la configuración de la fourbandera (si no lo estaba ya). A continuación, podríamos volver a definir eightpara incluir previamente, por así decirlo, la fourbandera,

<flag name="eight" value="12" /> <!-- 12 == 8|4 -->

Por último, si declara los atributos en un proyecto de biblioteca pero desea aplicarlos en diseños de otro proyecto (que depende de la biblioteca), deberá usar un prefijo de espacio de nombres que debe vincular en el elemento raíz XML. P.ej,

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:auto="http://schemas.android.com/apk/res-auto"
    ... >

    <com.example.MyWidget
        auto:myenum="two"
        auto:myflags="one|two"
        ... />

</RelativeLayout>
Rad Haring
fuente
Buenos puntos. Supongo que para alguien familiarizado con las enumeraciones de los lenguajes de programación, la diferencia entre una bandera y una enumeración sería una segunda naturaleza. (Ni siquiera me di cuenta de que el punto que estabas haciendo faltaba en la respuesta de Aleadam). Esta es definitivamente información adicional útil. +1
Ted Hopp
Esta respuesta apunta claramente a cómo diferenciar las enumeraciones de las banderas al definir atributos personalizados. Claramente me ayudó a usar banderas :) +1
ptitvinou