Declarar un elemento de IU de Android personalizado utilizando XML

Respuestas:

840

La Guía para desarrolladores de Android tiene una sección llamada Creación de componentes personalizados . Desafortunadamente, la discusión de los atributos XML solo cubre declarar el control dentro del archivo de diseño y no manejar los valores dentro de la inicialización de la clase. Los pasos son los siguientes:

1. Declarar atributos en values\attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MyCustomView">
        <attr name="android:text"/>
        <attr name="android:textColor"/>            
        <attr name="extraInformation" format="string" />
    </declare-styleable>
</resources>

Observe el uso de un nombre no calificado en la declare-styleableetiqueta. Los atributos de Android no estándar como extraInformationdeben tener su tipo declarado. Las etiquetas declaradas en la superclase estarán disponibles en subclases sin tener que volver a declararlas.

2. Crear constructores

Como hay dos constructores que usan una AttributeSetinicialización, es conveniente crear un método de inicialización separado para que los constructores llamen.

private void init(AttributeSet attrs) { 
    TypedArray a=getContext().obtainStyledAttributes(
         attrs,
         R.styleable.MyCustomView);

    //Use a
    Log.i("test",a.getString(
         R.styleable.MyCustomView_android_text));
    Log.i("test",""+a.getColor(
         R.styleable.MyCustomView_android_textColor, Color.BLACK));
    Log.i("test",a.getString(
         R.styleable.MyCustomView_extraInformation));

    //Don't forget this
    a.recycle();
}

R.styleable.MyCustomViewes un int[]recurso autogenerado donde cada elemento es la ID de un atributo. Los atributos se generan para cada propiedad en el XML agregando el nombre del atributo al nombre del elemento. Por ejemplo, R.styleable.MyCustomView_android_textcontiene el android_textatributo para MyCustomView. Los atributos se pueden recuperar del TypedArrayuso de varias getfunciones. Si el atributo no está definido en el definido en el XML, nullse devuelve. Excepto, por supuesto, si el tipo de retorno es primitivo, en cuyo caso se devuelve el segundo argumento.

Si no desea recuperar todos los atributos, es posible crear esta matriz manualmente. La ID para los atributos estándar de Android están incluidos android.R.attr, mientras que los atributos para este proyecto están incluidos R.attr.

int attrsWanted[]=new int[]{android.R.attr.text, R.attr.textColor};

Tenga en cuenta que usted debe no usar nada en android.R.styleable, según este hilo se puede cambiar en el futuro. Todavía está en la documentación, ya que es útil ver todas estas constantes en un solo lugar.

3. Úselo en archivos de diseño como layout\main.xml

Incluya la declaración del espacio de nombres xmlns:app="http://schemas.android.com/apk/res-auto"en el elemento xml de nivel superior. Los espacios de nombres proporcionan un método para evitar los conflictos que a veces ocurren cuando diferentes esquemas usan los mismos nombres de elementos (consulte este artículo para obtener más información). La URL es simplemente una forma de identificar esquemas de forma única: en realidad no es necesario alojar nada en esa URL . Si parece que esto no está haciendo nada, es porque en realidad no necesita agregar el prefijo del espacio de nombres a menos que necesite resolver un conflicto.

<com.mycompany.projectname.MyCustomView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@android:color/transparent"
    android:text="Test text"
    android:textColor="#FFFFFF"
    app:extraInformation="My extra information"
/> 

Haga referencia a la vista personalizada con el nombre completo.

Muestra de Android LabelView

Si desea un ejemplo completo, mire la muestra de vista de etiqueta de Android.

LabelView.java

 TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.LabelView);
 CharSequences=a.getString(R.styleable.LabelView_text);

attrs.xml

<declare-styleable name="LabelView">
    <attr name="text"format="string"/>
    <attr name="textColor"format="color"/>
    <attr name="textSize"format="dimension"/>
</declare-styleable>

custom_view_1.xml

<com.example.android.apis.view.LabelView
    android:background="@drawable/blue"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    app:text="Blue" app:textSize="20dp"/>

Esto está contenido en un LinearLayoutatributo con un espacio de nombres:xmlns:app="http://schemas.android.com/apk/res-auto"

Enlaces

Casebash
fuente
14
Me gustaría agregar que si su elemento raíz requiere su espacio de nombre personalizado, tendrá que agregar tanto el espacio de nombre de Android estándar como su propio espacio personalizado o de lo contrario puede experimentar errores de compilación.
Chase
11
Esta respuesta es el recurso más claro en Internet sobre parámetros XML personalizados que pude encontrar. Gracias Casebash.
Artem Russakovskii
2
Por alguna razón, el editor visual se niega a usar el valor de texto escrito para Android: texto, pero el dispositivo lo usa muy bien. Cómo ?
Desarrollador de Android
2
@androiddeveloper Parece que el editor Eclipse se niega a usar los valores para todos los atributos android:. Me gustaría saber si es una característica o un error
deej
44
¿Cuál es el propósito de xmlns: espacio de nombres de aplicaciones y res-auto?
IgorGanapolsky
91

Gran referencia ¡Gracias! Una adición a esto:

Si tiene un proyecto de biblioteca incluido que ha declarado atributos personalizados para una vista personalizada, debe declarar el espacio de nombres de su proyecto, no el de la biblioteca. P.ej:

Dado que la biblioteca tiene el paquete "com.example.library.customview" y el proyecto de trabajo tiene el paquete "com.example.customview", entonces:

No funcionará (muestra el error "error: no se encontró un identificador de recurso para el atributo 'newAttr' en el paquete 'com.example.library.customview'"):

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.library.customview"
        android:id="@+id/myView"
        app:newAttr="value" />

Trabajará:

<com.library.CustomView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res/com.example.customview"
        android:id="@+id/myView"
        app:newAttr="value" />
Andy
fuente
47
Esto se solucionó en la vista previa de ADT 17. Para usar el espacio de nombres de la aplicación de la biblioteca, declare xmlns:app="http://schemas.android.com/apk/res-auto"Vea el comentario 57 en code.google.com/p/android/issues/detail?id=9656
nmr
2
Incluyendo su espacio de nombres personalizado ahora devuelve un errorSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson
el espacio de nombres personalizado termina en res-auto porque estamos usando Android Studio y Gradle. De lo contrario (por ejemplo, algunas versiones de Eclipse), generalmente terminaría en lib / [nombre del paquete]
Universe
el espacio de nombres personalizado termina res-autoporque estamos usando Android Studio y Gradle. De lo contrario (por ejemplo, algunas versiones de Eclipse) generalmente terminaría en lib/[your package name]. iehttp://schemas.android.com/apk/lib/[your package name]
Universo
27

Adición a la respuesta más votada.

getStyledAttributes ()

Quiero agregar algunas palabras sobre el uso de getStyledAttributes (), cuando creamos una vista personalizada usando android: xxx atributos predefinidos. Especialmente cuando usamos TextAppearance.
Como se mencionó en "2. Crear constructores", la vista personalizada obtiene AttributeSet en su creación. Uso principal que podemos ver en el código fuente de TextView (API 16).

final Resources.Theme theme = context.getTheme();

// TextAppearance is inspected first, but let observe it later

TypedArray a = theme.obtainStyledAttributes(
            attrs, com.android.internal.R.styleable.TextView, defStyle, 0);

int n = a.getIndexCount();
for (int i = 0; i < n; i++) 
{
    int attr = a.getIndex(i);
    // huge switch with pattern value=a.getXXX(attr) <=> a.getXXX(a.getIndex(i))
}
a.recycle();

¿Qué podemos ver aquí?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
El conjunto de atributos se procesa por tema de acuerdo con la documentación. Los valores de los atributos se compilan paso a paso. Los primeros atributos se llenan del tema, luego los valores se reemplazan por los valores del estilo y, finalmente, los valores exactos de XML para la instancia de vista especial reemplazan a otros.
Matriz de atributos solicitados: com.android.internal.R.styleable.TextView
es una matriz ordinaria de constantes. Si estamos solicitando atributos estándar, podemos construir esta matriz manualmente.

Lo que no se menciona en la documentación: orden de los elementos TypedArray de resultados.
Cuando se declara una vista personalizada en attrs.xml, se generan constantes especiales para los índices de atributos. Y podemos extraer los valores de esta manera: a.getString(R.styleable.MyCustomView_android_text). Pero para el manual int[]no hay constantes. Supongo que getXXXValue (arrayIndex) funcionará bien.

Y otra pregunta es: "¿Cómo podemos reemplazar las constantes internas y solicitar atributos estándar?" Podemos usar los valores de android.R.attr. *.

Entonces, si queremos usar el atributo TextAppearance estándar en una vista personalizada y leer sus valores en el constructor, podemos modificar el código de TextView de esta manera:

ColorStateList textColorApp = null;
int textSize = 15;
int typefaceIndex = -1;
int styleIndex = -1;

Resources.Theme theme = context.getTheme();

TypedArray a = theme.obtainStyledAttributes(attrs, R.styleable.CustomLabel, defStyle, 0);
TypedArray appearance = null;
int apResourceId = a.getResourceId(R.styleable.CustomLabel_android_textAppearance, -1);
a.recycle();
if (apResourceId != -1)
{
    appearance = 
        theme.obtainStyledAttributes(apResourceId, new int[] { android.R.attr.textColor, android.R.attr.textSize, 
            android.R.attr.typeface, android.R.attr.textStyle });
}
if (appearance != null)
{
    textColorApp = appearance.getColorStateList(0);
    textSize = appearance.getDimensionPixelSize(1, textSize);
    typefaceIndex = appearance.getInt(2, -1);
    styleIndex = appearance.getInt(3, -1);

    appearance.recycle();
}

Donde se define CustomLabel:

<declare-styleable name="CustomLabel">
    <!-- Label text. -->
    <attr name="android:text" />
    <!-- Label text color. -->
    <attr name="android:textColor" />
    <!-- Combined text appearance properties. -->
    <attr name="android:textAppearance" />
</declare-styleable>

Tal vez, estoy equivocado de alguna manera, pero la documentación de Android en getStyledAttributes () es muy pobre.

Componente de interfaz de usuario estándar extendido

Al mismo tiempo, solo podemos extender el componente de interfaz de usuario estándar, utilizando todos sus atributos declarados. Este enfoque no es tan bueno, porque TextView, por ejemplo, declara muchas propiedades. Y será imposible implementar la funcionalidad completa en anular onMeasure () y onDraw ().

Pero podemos sacrificar la reutilización teórica de componentes personalizados. Diga "Sé exactamente qué características usaré" y no comparta código con nadie.

Entonces podemos implementar constructor CustomComponent(Context, AttributeSet, defStyle). Después de llamar super(...)tendremos todos los atributos analizados y disponibles a través de métodos getter.

yuriy.weiss
fuente
Cómo funcionan los atributos predefinidos de android: xxx en eclipse gui designer?
deej
Dichos atributos son reconocidos por el complemento Eclipse ADT en el editor de propiedades. Puedo ver los valores predeterminados de mi estilo, si algunos valores no están definidos. Y no olvide agregar la anotación @RemoteView a su clase.
yuriy.weiss
No puedo hacer que funcione. Eclipse sigue cargando nulos para getText y arrojando android.content.res.Resources $ NotFoundException para getResourceId, aunque la aplicación se ejecuta bien en un dispositivo.
deej
Lo siento, no puedo ayudarte. Solo he creado un proyecto de demostración para probar posibilidades, y no he encontrado tales errores.
yuriy.weiss
Esto es mucho mejor que asignar atributos personalizados de una vista personalizada a los atributos integrados de las vistas integradas que contiene.
samis
13

Parece que Google ha actualizado su página de desarrollador y ha agregado varios entrenamientos allí.

Uno de ellos se ocupa de la creación de vistas personalizadas y se puede encontrar aquí.

mitch000001
fuente
5

Muchas gracias por la primera respuesta.

En cuanto a mí, solo tuve un problema con eso. Al inflar mi vista, tuve un error: java.lang.NoSuchMethodException: MyView (Context, Attributes)

Lo resolví creando un nuevo constructor:

public MyView(Context context, AttributeSet attrs) {
     super(context, attrs);
     // some code
}

Espero que esto ayude!

usuario2346922
fuente
0

Puede incluir cualquier archivo de diseño en otro archivo de diseño como-

             <RelativeLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="10dp"
                android:layout_marginRight="30dp" >

                <include
                    android:id="@+id/frnd_img_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_imagefile"/>

                <include
                    android:id="@+id/frnd_video_file"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    layout="@layout/include_video_lay" />

                <ImageView
                    android:id="@+id/downloadbtn"
                    android:layout_width="30dp"
                    android:layout_height="30dp"
                    android:layout_centerInParent="true"
                    android:src="@drawable/plus"/>
            </RelativeLayout>

aquí los archivos de diseño en la etiqueta de inclusión son otros archivos de diseño .xml en la misma carpeta res.

Akshay Paliwal
fuente
He intentado esto, el problema que tengo es que el diseño incluido no se puede 'adaptar', no puede crear genéricos. Por ejemplo, cuando incluyo un botón de manera similar, si trato de configurar el texto en el xml, funciona.
cfl