Déclaration d'un élément d'interface utilisateur Android personnalisé à l'aide de XML

Réponses:

840

Le Guide du développeur Android comporte une section intitulée Création de composants personnalisés . Malheureusement, la discussion sur les attributs XML ne couvre que la déclaration du contrôle dans le fichier de mise en page et non la gestion des valeurs dans l'initialisation de la classe. Les étapes sont les suivantes:

1. Déclarez les attributs dans 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>

Notez l'utilisation d'un nom non qualifié dans la declare-styleablebalise. Les attributs Android non standard comme le extraInformationbesoin de déclarer leur type. Les balises déclarées dans la superclasse seront disponibles dans les sous-classes sans avoir à être redéclarées.

2. Créer des constructeurs

Puisqu'il y a deux constructeurs qui utilisent un AttributeSetpour l'initialisation, il est pratique de créer une méthode d'initialisation distincte pour les constructeurs à appeler.

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.MyCustomViewest une int[]ressource autogénérée où chaque élément est l'ID d'un attribut. Les attributs sont générés pour chaque propriété dans le XML en ajoutant le nom d'attribut au nom de l'élément. Par exemple, R.styleable.MyCustomView_android_textcontient l' android_textattribut pour MyCustomView. Les attributs peuvent ensuite être récupérés à l' TypedArrayaide de diverses getfonctions. Si l'attribut n'est pas défini dans le défini dans le XML, il nullest renvoyé. Sauf, bien sûr, si le type de retour est une primitive, auquel cas le deuxième argument est renvoyé.

Si vous ne souhaitez pas récupérer tous les attributs, il est possible de créer ce tableau manuellement. L'ID pour les attributs Android standard est inclus dans android.R.attr, tandis que les attributs pour ce projet sont dans R.attr.

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

Veuillez noter que vous ne devez rien utiliser dans android.R.styleable, selon ce fil, cela pourrait changer à l'avenir. Il est toujours dans la documentation car il est utile de visualiser toutes ces constantes au même endroit.

3. Utilisez-le dans des fichiers de mise en page tels que layout\main.xml

Incluez la déclaration d'espace de noms xmlns:app="http://schemas.android.com/apk/res-auto"dans l'élément xml de niveau supérieur. Les espaces de noms fournissent une méthode pour éviter les conflits qui se produisent parfois lorsque différents schémas utilisent les mêmes noms d'élément (voir cet article pour plus d'informations). L'URL est simplement une manière d'identifier de manière unique les schémas - rien n'a réellement besoin d'être hébergé sur cette URL . Si cela ne semble rien faire, c'est parce que vous n'avez pas réellement besoin d'ajouter le préfixe d'espace de noms, sauf si vous devez résoudre un conflit.

<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"
/> 

Référencez la vue personnalisée en utilisant le nom complet.

Exemple Android LabelView

Si vous voulez un exemple complet, regardez l'exemple de vue d'étiquette 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"/>

Il est contenu dans un LinearLayoutavec un attribut namespace:xmlns:app="http://schemas.android.com/apk/res-auto"

Liens

Casebash
la source
14
Je voudrais ajouter que si votre élément racine nécessite votre espace de noms personnalisé, vous devrez ajouter à la fois l'espace de noms Android standard et votre propre espace personnalisé, sinon vous pourriez rencontrer des erreurs de construction.
Chase
11
Cette réponse est la ressource la plus claire sur Internet sur les paramètres XML personnalisés que j'ai pu trouver. Merci, Casebash.
Artem Russakovskii
2
pour une raison quelconque, l'éditeur visuel refuse d'utiliser la valeur du texte écrit pour Android: texte, mais l'appareil l'utilise très bien. comment venir ?
développeur Android
2
@androiddeveloper Il semble que l'éditeur Eclipse refuse d'utiliser les valeurs pour tous les attributs android :. Je voudrais savoir s'il s'agit d'une fonctionnalité ou d'un bug
deej
4
Quel est le but de xmlns: espace de noms d'application et res-auto?
IgorGanapolsky
91

Grande référence. Merci! Un ajout à cela:

S'il vous arrive d'avoir un projet de bibliothèque inclus qui a déclaré des attributs personnalisés pour une vue personnalisée, vous devez déclarer votre espace de noms de projet, pas celui de la bibliothèque. Par exemple:

Étant donné que la bibliothèque a le package "com.example.library.customview" et que le projet de travail a le package "com.example.customview", alors:

Ne fonctionnera pas (affiche l'erreur "error: Aucun identifiant de ressource trouvé pour l'attribut 'newAttr' dans le package '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" />

Marchera:

<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
la source
47
Cela a été ~ corrigé dans l'aperçu ADT 17. Pour utiliser l'espace de noms de l' application de la bibliothèque déclarer xmlns:app="http://schemas.android.com/apk/res-auto"Voir commentaire 57 code.google.com/p/android/issues/detail?id=9656
NMR
2
L'inclusion de votre espace de noms personnalisé renvoie désormais une erreurSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson
l'espace de noms personnalisé se termine par res-auto car nous utilisons Android Studio et Gradle. Sinon (par exemple certaines versions d'Eclipse), il se termine généralement par lib / [votre nom de package]
Univers
l'espace de noms personnalisé se termine res-autoparce que nous utilisons Android Studio et Gradle. Sinon (par exemple certaines versions d'Eclipse), cela se terminerait généralement par lib/[your package name]. iehttp://schemas.android.com/apk/lib/[your package name]
Univers
27

Ajout à la réponse la plus votée.

getStyledAttributes ()

Je veux ajouter quelques mots à propos de l'utilisation de obtenezStyledAttributes (), lorsque nous créons une vue personnalisée en utilisant des attributs prédéfinis android: xxx. Surtout lorsque nous utilisons TextAppearance.
Comme mentionné dans «2. Création de constructeurs», la vue personnalisée obtient AttributeSet lors de sa création. Utilisation principale que nous pouvons voir dans le code source 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();

Que pouvons-nous voir ici?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
L'ensemble d'attributs est traité par thème selon la documentation. Les valeurs d'attribut sont compilées étape par étape. Les premiers attributs sont remplis à partir du thème, puis les valeurs sont remplacées par des valeurs à partir du style, et enfin les valeurs exactes de XML pour une instance de vue spéciale remplacent les autres.
Tableau d'attributs demandés - com.android.internal.R.styleable.TextView
Il s'agit d'un tableau ordinaire de constantes. Si nous demandons des attributs standard, nous pouvons construire ce tableau manuellement.

Ce qui n'est pas mentionné dans la documentation - ordre des éléments TypedArray résultants.
Lorsque la vue personnalisée est déclarée dans attrs.xml, des constantes spéciales pour les index d'attribut sont générées. Et nous pouvons extraire des valeurs de cette façon: a.getString(R.styleable.MyCustomView_android_text). Mais pour le manuel int[]il n'y a pas de constantes. Je suppose que getXXXValue (arrayIndex) fonctionnera bien.

Et une autre question est: "Comment pouvons-nous remplacer les constantes internes et demander des attributs standard?" Nous pouvons utiliser les valeurs android.R.attr. *.

Donc, si nous voulons utiliser l'attribut TextAppearance standard dans la vue personnalisée et lire ses valeurs dans le constructeur, nous pouvons modifier le code de TextView de cette façon:

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();
}

Où CustomLabel est défini:

<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>

Peut-être que je me trompe d'une manière ou d'une autre, mais la documentation Android sur obtenezStyledAttributes () est très pauvre.

Extension du composant d'interface utilisateur standard

Dans le même temps, nous pouvons simplement étendre le composant d'interface utilisateur standard, en utilisant tous ses attributs déclarés. Cette approche n'est pas si bonne, car TextView, par exemple, déclare beaucoup de propriétés. Et il sera impossible d'implémenter toutes les fonctionnalités dans onMeasure () et onDraw () remplacés.

Mais nous pouvons sacrifier une large réutilisation théorique du composant personnalisé. Dites «je sais exactement quelles fonctionnalités j'utiliserai» et ne partagez pas le code avec qui que ce soit.

Ensuite, nous pouvons implémenter le constructeur CustomComponent(Context, AttributeSet, defStyle). Après l'appel super(...), tous les attributs seront analysés et disponibles via les méthodes getter.

yuriy.weiss
la source
les attributs prédéfinis android: xxx fonctionnent-ils dans eclipse gui designer?
deej
Ces attributs sont reconnus par le plugin Eclipse ADT dans l'éditeur de propriétés. Je peux voir les valeurs par défaut de mon style, si certaines valeurs ne sont pas définies. Et n'oubliez pas d'ajouter l'annotation @RemoteView à votre classe.
yuriy.weiss
Je ne peux pas le faire fonctionner. Eclipse continue de charger des valeurs nulles pour getText et de lancer android.content.res.Resources $ NotFoundException pour getResourceId, bien que l'application fonctionne bien sur un appareil.
deej
Désolé, je ne peux pas vous aider. J'ai créé uniquement un projet de démonstration pour tester les possibilités, et je n'ai pas rencontré de telles erreurs.
yuriy.weiss
C'est bien mieux que de mapper les attributs personnalisés d'une vue personnalisée aux attributs intégrés des vues intégrées contenus dans.
samis
13

Il semble que Google ait mis à jour sa page développeur et y ait ajouté diverses formations.

L'un d'eux traite de la création de vues personnalisées et peut être trouvé ici

mitch000001
la source
5

Merci beaucoup pour la première réponse.

Quant à moi, je n'ai eu qu'un seul problème avec ça. Lors du gonflage de ma vue, j'ai eu un bug: java.lang.NoSuchMethodException: MyView (Context, Attributes)

Je l'ai résolu en créant un nouveau constructeur:

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

J'espère que cela vous aidera!

user2346922
la source
0

Vous pouvez inclure n’importe quel fichier de mise en page dans un autre fichier de mise en page.

             <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>

ici, les fichiers de disposition dans la balise include sont d'autres fichiers de disposition .xml dans le même dossier res.

Akshay Paliwal
la source
J'ai essayé, le problème que j'ai avec cela est que la disposition incluse ne peut pas être «adaptée», ne peut pas créer de génériques. Par exemple, lorsque j'inclus un bouton de la même manière, si j'essaie de définir le texte dans le xml, cela fonctionne.
cfl