Définir des attr personnalisés

472

J'ai besoin d'implémenter mes propres attributs comme dans com.android.R.attr

Je n'ai rien trouvé dans la documentation officielle, j'ai donc besoin d'informations sur la façon de définir ces attr et comment les utiliser à partir de mon code.

Alexander Oleynikov
la source
20
Ces documents peuvent être plus récents que votre message, mais afin de maintenir ce courant, vous pouvez trouver une bonne documentation officielle pour les attributs ici: developer.android.com/training/custom-views/…
OYRM
Je recommande un bel article avec un exemple sur les attributs personnalisés: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński
un petit exemple de travail peut être utile: github.com/yujiaao/MergeLayout1
Yu Jiaao

Réponses:

971

Actuellement, la meilleure documentation est la source. Vous pouvez y jeter un œil ici (attrs.xml) .

Vous pouvez définir des attributs dans l' <resources>élément supérieur ou à l'intérieur d'un <declare-styleable>élément. Si je vais utiliser un attr à plus d'un endroit, je le mets dans l'élément racine. Notez que tous les attributs partagent le même espace de noms global. Cela signifie que même si vous créez un nouvel attribut à l'intérieur d'un <declare-styleable>élément, il peut être utilisé à l'extérieur de celui-ci et vous ne pouvez pas créer un autre attribut avec le même nom d'un type différent.

Un <attr>élément a deux attributs xml nameet format. namevous permet d' appeler quelque chose et c'est la façon dont vous finissez par y faire référence dans le code, par exemple R.attr.my_attribute. L' formatattribut peut avoir différentes valeurs selon le «type» d'attribut souhaité.

  • référence - si elle fait référence à un autre identifiant de ressource (par exemple, "@ color / my_color", "@ layout / my_layout")
  • Couleur
  • booléen
  • dimension
  • flotte
  • entier
  • chaîne
  • fraction
  • enum - normalement implicitement défini
  • indicateur - normalement implicitement défini

Vous pouvez définir le format à plusieurs types en utilisant |, par exemple, format="reference|color".

enum les attributs peuvent être définis comme suit:

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

flag les attributs sont similaires, sauf que les valeurs doivent être définies pour pouvoir être bitées ensemble:

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

En plus des attributs, il y a l' <declare-styleable>élément. Cela vous permet de définir les attributs qu'une vue personnalisée peut utiliser. Pour ce faire, spécifiez un <attr>élément, s'il a été défini précédemment, vous ne spécifiez pas le format. Si vous souhaitez réutiliser un attr android, par exemple, android: gravity, vous pouvez le faire dans le name, comme suit.

Un exemple de vue personnalisée <declare-styleable>:

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

Lors de la définition de vos attributs personnalisés en XML sur votre vue personnalisée, vous devez effectuer quelques opérations. Vous devez d'abord déclarer un espace de noms pour trouver vos attributs. Vous faites cela sur l'élément de disposition racine. Normalement, il y en a seulement xmlns:android="http://schemas.android.com/apk/res/android". Vous devez maintenant ajouter également xmlns:whatever="http://schemas.android.com/apk/res-auto".

Exemple:

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

Enfin, pour accéder à cet attribut personnalisé, vous le faites normalement dans le constructeur de votre vue personnalisée comme suit.

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

La fin. :)

Rich Schuler
la source
14
Voici un exemple de projet démontrant des attributs personnalisés pour une utilisation avec une coutume View: github.com/commonsguy/cw-advandroid/tree/master/Views/...
CommonsWare
7
Si vous utilisez des attr personnalisés à partir d'un projet de bibliothèque: consultez cette question: stackoverflow.com/questions/5819369/… - Cela semble fonctionner si vous utilisez xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- pas de copie d'attrs.xml. Notez que le chemin URI de l'espace de noms doit être / apk / * lib * et non / apk / res.
thom_nic
2
@ThomNichols, l' apk/libastuce n'a pas fonctionné pour moi sur les attributs personnalisés avec le format de référence d'un projet de bibliothèque. Ce qui a fonctionné était d'utiliser apk/res-auto, comme suggéré dans stackoverflow.com/a/13420366/22904 juste en dessous et également dans stackoverflow.com/a/10217752
Giulio Piancastelli
1
Citant @Qberticus: "les attributs des drapeaux sont similaires sauf que les valeurs doivent être définies pour pouvoir être bitées ensemble". À mon avis, c'est en quelque sorte sous-estimer la principale différence entre enumet flag: la première nous permet de choisir une et une seule valeur, la seconde nous permet d'en combiner plusieurs. J'ai écrit une réponse plus longue dans une question similaire ici , et maintenant que j'ai trouvé cette question, je me suis dit que j'y lierais un lien.
Rad Haring
5
a.recycle()est très important ici pour libérer de la mémoire
Tash Pemhiwa
87

La réponse de Qberticus est bonne, mais il manque un détail utile. Si vous les implémentez dans une bibliothèque, remplacez:

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

avec:

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

Sinon, l'application qui utilise la bibliothèque aura des erreurs d'exécution.

Neil Miller
la source
3
Cela n'a été ajouté que récemment ... je pense qu'il y a quelques semaines. Certes, il a été ajouté longtemps après que Qberticus ait écrit sa réponse.
ArtOfWarfare
12
Je pense que c'est plus ancien que cela, mais il a certainement été ajouté longtemps après que Qberticus ait écrit sa réponse. Ne le blâmant pas du tout, ajoutant simplement un détail utile.
Neil Miller
11
J'ai mis à jour la réponse de Qbericus pour utiliser apk / res-auto pour éviter la confusion.
Intrications
15

La réponse ci-dessus couvre tout en détail, à l'exception de quelques éléments.

Tout d'abord, s'il n'y a pas de styles, la (Context context, AttributeSet attrs)signature de la méthode sera utilisée pour instancier la préférence. Dans ce cas, utilisez simplement context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)pour obtenir le TypedArray.

Deuxièmement, il ne couvre pas comment gérer les ressources plaurales (chaînes de quantité). Ceux-ci ne peuvent pas être traités à l'aide de TypedArray. Voici un extrait de code de mon SeekBarPreference qui définit le résumé de la préférence en formatant sa valeur en fonction de la valeur de la préférence. Si le xml de la préférence définit android: summary dans une chaîne de texte ou une chaîne de caractères, la valeur de la préférence est formatée dans la chaîne (elle doit contenir% d, pour récupérer la valeur). Si android: summary est défini sur une ressource plaurals, il est utilisé pour formater le résultat.

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

  • Ceci n'est donné qu'à titre d'exemple, cependant, si vous voulez être tenté de définir le résumé sur l'écran des préférences, vous devez alors appeler notifyChanged()la onDialogClosedméthode des préférences .
Steve Waring
la source
5

L'approche traditionnelle est pleine de code standard et de gestion des ressources maladroite. C'est pourquoi j'ai créé le cadre Spyglass . Pour montrer comment cela fonctionne, voici un exemple montrant comment créer une vue personnalisée qui affiche un titre de chaîne.

Étape 1: créez une classe de vue personnalisée.

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

Étape 2: définissez un attribut de chaîne dans le values/attrs.xmlfichier de ressources:

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

Étape 3: appliquez l' @StringHandlerannotation à la setTitleméthode pour indiquer au framework Spyglass de router la valeur d'attribut vers cette méthode lorsque la vue est gonflée.

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

Maintenant que votre classe a une annotation Spyglass, le framework Spyglass la détectera au moment de la compilation et générera automatiquement la CustomView_SpyglassCompanionclasse.

Étape 4: utilisez la classe générée dans la initméthode de la vue personnalisée :

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

C'est ça. Désormais, lorsque vous instanciez la classe à partir de XML, le compagnon Spyglass interprète les attributs et effectue l'appel de méthode requis. Par exemple, si nous gonflons la disposition suivante, setTitlenous serons appelés avec "Hello, World!"comme argument.

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

Le cadre n'est pas limité aux ressources de chaîne a beaucoup d'annotations différentes pour gérer d'autres types de ressources. Il comporte également des annotations pour définir des valeurs par défaut et pour transmettre des valeurs d'espace réservé si vos méthodes ont plusieurs paramètres.

Jetez un œil au dépôt Github pour plus d'informations et d'exemples.

Helios
la source
Vous pouvez obtenir la même chose avec Google Data Binding - s'il n'y a pas de liaison d'attribut pour un attribut spécifique, GDB essaie de trouver la méthode set * et l'utilise à la place. Dans ce cas, il faudrait écrire, disons android:title="@{&quot;Hello, world!&quot;}".
Spook
0

si vous omettez l' formatattribut de l' attrélément, vous pouvez l'utiliser pour référencer une classe à partir de dispositions XML.

  • exemple de attrs.xml .
  • Android Studio comprend que la classe est référencée à partir de XML
    • c'est à dire
      • Refactor > Rename travaux
      • Find Usages travaux
      • etc...

ne spécifiez pas d' formatattribut dans ... / 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>

utilisez-le dans un fichier de mise en page ... / 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>

analyser la classe dans le code d'initialisation de votre vue ... / 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
la source