Definición de atributos personalizados

Resuelto Alexander Oleynikov asked hace 54 años • 6 respuestas

Necesito implementar mis propios atributos como encom.android.R.attr

No encontré nada en la documentación oficial, por lo que necesito información sobre cómo definir estos atributos y cómo usarlos desde mi código.

Alexander Oleynikov avatar Jan 01 '70 08:01 Alexander Oleynikov
Aceptado

Actualmente la mejor documentación es la fuente. Puedes echarle un vistazo aquí (attrs.xml) .

Puede definir atributos en el <resources>elemento superior o dentro de un <declare-styleable>elemento. Si voy a usar un atributo en más de un lugar, lo coloco en el elemento raíz. Tenga en cuenta que todos los atributos comparten el mismo espacio de nombres global. Eso significa que incluso si crea un nuevo atributo dentro de un <declare-styleable>elemento, puede usarse fuera de él y no puede crear otro atributo con el mismo nombre de un tipo diferente.

Un <attr>elemento tiene dos atributos xml namey format. namete permite llamarlo de alguna manera y así es como terminas refiriéndote a él en el código, por ejemplo, R.attr.my_attribute. El formatatributo puede tener diferentes valores dependiendo del 'tipo' de atributo que desee.

  • referencia: si hace referencia a otra identificación de recurso (por ejemplo, "@color/my_color", "@layout/my_layout")
  • color
  • booleano
  • dimensión
  • flotar
  • entero
  • cadena
  • fracción
  • enumeración - normalmente definida implícitamente
  • bandera - normalmente definida implícitamente

Puede configurar el formato en varios tipos utilizando |, por ejemplo, format="reference|color".

enumLos atributos se pueden definir de la siguiente manera:

<attr name="my_enum_attr">
  <enum name="value1" value="1" />
  <enum name="value2" value="2" />
</attr>

flagLos atributos son similares excepto que los valores deben definirse para que puedan unirse entre sí:

<attr name="my_flag_attr">
  <flag name="fuzzy" value="0x01" />
  <flag name="cold" value="0x02" />
</attr>

Además de los atributos está el <declare-styleable>elemento. Esto le permite definir atributos que puede usar una vista personalizada. Esto lo haces especificando un <attr>elemento, si se definió previamente no especificas el format. Si desea reutilizar un atributo de Android, por ejemplo, android:gravity, puede hacerlo en el archivo name, de la siguiente manera.

Un ejemplo de una vista personalizada <declare-styleable>:

<declare-styleable name="MyCustomView">
  <attr name="my_custom_attribute" />
  <attr name="android:gravity" />
</declare-styleable>

Al definir sus atributos personalizados en XML en su vista personalizada, debe hacer algunas cosas. Primero debes declarar un espacio de nombres para encontrar tus atributos. Esto se hace en el elemento de diseño raíz. Normalmente solo hay xmlns:android="http://schemas.android.com/apk/res/android". Ahora también debes agregar xmlns:whatever="http://schemas.android.com/apk/res-auto".

Ejemplo:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:whatever="http://schemas.android.com/apk/res-auto"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <org.example.mypackage.MyCustomView
      android:layout_width="fill_parent"
      android:layout_height="wrap_content"
      android:gravity="center"
      whatever:my_custom_attribute="Hello, world!" />
</LinearLayout>

Finalmente, para acceder a ese atributo personalizado, normalmente lo hace en el constructor de su vista personalizada de la siguiente manera.

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

  TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView, defStyle, 0);

  String str = a.getString(R.styleable.MyCustomView_my_custom_attribute);

  //do something with str

  a.recycle();
}

El fin. :)

Rich Schuler avatar Aug 09 '2010 16:08 Rich Schuler

La respuesta de Qberticus es buena, pero falta un detalle útil. Si está implementando estos en una biblioteca, reemplace:

xmlns:whatever="http://schemas.android.com/apk/res/org.example.mypackage"

con:

xmlns:whatever="http://schemas.android.com/apk/res-auto"

De lo contrario, la aplicación que utiliza la biblioteca tendrá errores de ejecución.

Neil Miller avatar Nov 16 '2012 16:11 Neil Miller

La respuesta anterior cubre todo con gran detalle, excepto un par de cosas.

En primer lugar, si no hay estilos, (Context context, AttributeSet attrs)se utilizará la firma del método para crear una instancia de la preferencia. En este caso, simplemente utilícelo context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)para obtener TypedArray.

En segundo lugar, no cubre cómo tratar con recursos plurales (cadenas de cantidad). Estos no se pueden solucionar utilizando TypedArray. Aquí hay un fragmento de código de mi SeekBarPreference que establece el resumen de la preferencia formateando su valor de acuerdo con el valor de la preferencia. Si el xml de la preferencia establece android:summary en una cadena de texto o un recurso de cadena, el valor de la preferencia se formatea en la cadena (debe tener %d, para recoger el valor). Si android:summary está configurado como un recurso plaurals, se usa para formatear el resultado.

// Use your own name space if not using an android resource.
final static private String ANDROID_NS = 
    "http://schemas.android.com/apk/res/android";
private int pluralResource;
private Resources resources;
private String summary;

public SeekBarPreference(Context context, AttributeSet attrs) {
    // ...
    TypedArray attributes = context.obtainStyledAttributes(
        attrs, R.styleable.SeekBarPreference);
    pluralResource =  attrs.getAttributeResourceValue(ANDROID_NS, "summary", 0);
    if (pluralResource !=  0) {
        if (! resources.getResourceTypeName(pluralResource).equals("plurals")) {
            pluralResource = 0;
        }
    }
    if (pluralResource ==  0) {
        summary = attributes.getString(
            R.styleable.SeekBarPreference_android_summary);
    }
    attributes.recycle();
}

@Override
public CharSequence getSummary() {
    int value = getPersistedInt(defaultValue);
    if (pluralResource != 0) {
        return resources.getQuantityString(pluralResource, value, value);
    }
    return (summary == null) ? null : String.format(summary, value);
}

  • Esto se proporciona solo como ejemplo; sin embargo, si desea configurar el resumen en la pantalla de preferencias, debe llamar notifyChanged()al onDialogClosedmétodo de preferencias.
Steve Waring avatar Sep 03 '2014 15:09 Steve Waring

El enfoque tradicional está lleno de código repetitivo y un manejo torpe de recursos. Por eso creé el marco Spyglass . Para demostrar cómo funciona, aquí hay un ejemplo que muestra cómo crear una vista personalizada que muestre un título de cadena.

Paso 1: cree una clase de vista personalizada.

public class CustomView extends FrameLayout {
    private TextView titleView;

    public CustomView(Context context) {
        super(context);
        init(null, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(attrs, 0, 0);
    }

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(attrs, defStyleAttr, 0);
    }

    @RequiresApi(21)
    public CustomView(
            Context context, 
            AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {

        super(context, attrs, defStyleAttr, defStyleRes);
        init(attrs, defStyleAttr, defStyleRes);
    }

    public void setTitle(String title) {
        titleView.setText(title);
    }

    private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        inflate(getContext(), R.layout.custom_view, this);

        titleView = findViewById(R.id.title_view);
    }
}

Paso 2: Defina un atributo de cadena en el values/attrs.xmlarchivo de recursos:

<resources>
    <declare-styleable name="CustomView">
        <attr name="title" format="string"/>
    </declare-styleable>
</resources>

Paso 3: aplique la @StringHandleranotación al setTitlemétodo para indicarle al marco de Spyglass que dirija el valor del atributo a este método cuando la vista esté inflada.

@HandlesString(attributeId = R.styleable.CustomView_title)
public void setTitle(String title) {
    titleView.setText(title);
}

Ahora que su clase tiene una anotación Spyglass, el marco Spyglass la detectará en tiempo de compilación y generará automáticamente la CustomView_SpyglassCompanionclase.

Paso 4: use la clase generada en el initmétodo de la vista personalizada:

private void init(AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    inflate(getContext(), R.layout.custom_view, this);

    titleView = findViewById(R.id.title_view);

    CustomView_SpyglassCompanion
            .builder()
            .withTarget(this)
            .withContext(getContext())
            .withAttributeSet(attrs)
            .withDefaultStyleAttribute(defStyleAttr)
            .withDefaultStyleResource(defStyleRes)
            .build()
            .callTargetMethodsNow();
}

Eso es todo. Ahora, cuando crea una instancia de la clase desde XML, el complemento Spyglass interpreta los atributos y realiza la llamada al método requerido. Por ejemplo, si inflamos el siguiente diseño, setTitlese llamará con "Hello, World!"como argumento.

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:width="match_parent"
    android:height="match_parent">

    <com.example.CustomView
        android:width="match_parent"
        android:height="match_parent"
        app:title="Hello, World!"/>
</FrameLayout>

El marco no se limita a recursos de cadenas y tiene muchas anotaciones diferentes para manejar otros tipos de recursos. También tiene anotaciones para definir valores predeterminados y para pasar valores de marcador de posición si sus métodos tienen múltiples parámetros.

Eche un vistazo al repositorio de Github para obtener más información y ejemplos.

Helios avatar Nov 20 '2017 12:11 Helios

Si omite el formatatributo del attrelemento, puede usarlo para hacer referencia a una clase desde diseños XML.

  • ejemplo de attrs.xml .
  • Android Studio entiende que se hace referencia a la clase desde XML
    • es decir
      • Refactor > Renameobras
      • Find Usagesobras
      • etcétera...

no especifique un formatatributo en .../src/main/res/values/attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="MyCustomView">
        ....
        <attr name="give_me_a_class"/>
        ....
    </declare-styleable>

</resources>

Úselo en algún archivo de diseño .../src/main/res/layout/activity__main_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<SomeLayout
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- make sure to use $ dollar signs for nested classes -->
    <MyCustomView
        app:give_me_a_class="class.type.name.Outer$Nested/>

    <MyCustomView
        app:give_me_a_class="class.type.name.AnotherClass/>

</SomeLayout>

analiza la clase en el código de inicialización de tu vista .../src/main/java/.../MyCustomView.kt

class MyCustomView(
        context:Context,
        attrs:AttributeSet)
    :View(context,attrs)
{
    // parse XML attributes
    ....
    private val giveMeAClass:SomeCustomInterface
    init
    {
        context.theme.obtainStyledAttributes(attrs,R.styleable.ColorPreference,0,0).apply()
        {
            try
            {
                // very important to use the class loader from the passed-in context
                giveMeAClass = context::class.java.classLoader!!
                        .loadClass(getString(R.styleable.MyCustomView_give_me_a_class))
                        .newInstance() // instantiate using 0-args constructor
                        .let {it as SomeCustomInterface}
            }
            finally
            {
                recycle()
            }
        }
    }
Eric avatar Sep 20 '2019 06:09 Eric