Benutzerdefinierte Attribute definieren


472

Ich muss meine eigenen Attribute wie in implementieren com.android.R.attr

In der offiziellen Dokumentation wurde nichts gefunden, daher benötige ich Informationen zum Definieren dieser Attribute und zur Verwendung in meinem Code.


20
Diese Dokumente sind möglicherweise neuer als Ihr Beitrag, aber um dies auf dem
neuesten Stand

Ich empfehle einen schönen Artikel mit einem Beispiel über die benutzerdefinierten Attribute: amcmobileware.org/android/blog/2016/09/11/custom-attributes
Arkadiusz Cieśliński

Ein kleines Arbeitsbeispiel kann hilfreich sein: github.com/yujiaao/MergeLayout1
Yu Jiaao

Antworten:


971

Derzeit ist die beste Dokumentation die Quelle. Sie können es hier ansehen (attrs.xml) .

Sie können Attribute im obersten <resources>Element oder innerhalb eines <declare-styleable>Elements definieren. Wenn ich ein attr an mehr als einer Stelle verwenden möchte, füge ich es in das Stammelement ein. Beachten Sie, dass alle Attribute denselben globalen Namespace verwenden. Das heißt, selbst wenn Sie ein neues Attribut innerhalb eines <declare-styleable>Elements erstellen , kann es außerhalb des Elements verwendet werden, und Sie können kein anderes Attribut mit demselben Namen eines anderen Typs erstellen.

Ein <attr>Element hat zwei XML-Attribute nameund format. nameSie können es etwas nennen, und so verweisen Sie im Code darauf, z R.attr.my_attribute. Das formatAttribut kann je nach gewünschtem Attributtyp unterschiedliche Werte haben.

  • Referenz - wenn es auf eine andere Ressourcen-ID verweist (z. B. "@ color / my_color", "@ layout / my_layout")
  • Farbe
  • Boolescher Wert
  • Abmessungen
  • schweben
  • ganze Zahl
  • Zeichenfolge
  • Fraktion
  • enum - normalerweise implizit definiert
  • flag - normalerweise implizit definiert

Sie können das Format auf mehrere Typen einstellen, indem Sie |z format="reference|color".

enum Attribute können wie folgt definiert werden:

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

flag Attribute sind ähnlich, außer dass die Werte definiert werden müssen, damit sie zusammengefügt werden können:

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

Neben Attributen gibt es das <declare-styleable>Element. Auf diese Weise können Sie Attribute definieren, die eine benutzerdefinierte Ansicht verwenden kann. Sie tun dies, indem Sie ein <attr>Element angeben format. Wenn es zuvor definiert wurde, geben Sie das nicht an . Wenn Sie ein Android-Attribut, beispielsweise Android: Gravity, wiederverwenden möchten, können Sie dies namewie folgt tun .

Ein Beispiel für eine benutzerdefinierte Ansicht <declare-styleable>:

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

Wenn Sie Ihre benutzerdefinierten Attribute in XML in Ihrer benutzerdefinierten Ansicht definieren, müssen Sie einige Dinge tun. Zuerst müssen Sie einen Namespace deklarieren, um Ihre Attribute zu finden. Sie tun dies für das Root-Layout-Element. Normalerweise gibt es nur xmlns:android="http://schemas.android.com/apk/res/android". Sie müssen jetzt auch hinzufügen xmlns:whatever="http://schemas.android.com/apk/res-auto".

Beispiel:

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

Um auf dieses benutzerdefinierte Attribut zuzugreifen, tun Sie dies normalerweise im Konstruktor Ihrer benutzerdefinierten Ansicht wie folgt.

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

Das Ende. :) :)


14
Hier ist ein Beispielprojekt, das benutzerdefinierte Attribute zur Verwendung mit einem benutzerdefinierten demonstriert View: github.com/commonsguy/cw-advandroid/tree/master/Views/…
CommonsWare

7
Wenn Sie benutzerdefinierte Attribute aus einem Bibliotheksprojekt verwenden: siehe folgende Frage: stackoverflow.com/questions/5819369/… - Es scheint zu funktionieren, wenn Sie verwenden xmlns:my="http://schemas.android.com/apk/lib/my.namespace"- kein Kopieren von attrs.xml. Beachten Sie, dass der Namespace-URI-Pfad / apk / * lib * not / apk / res sein muss.
thom_nic

2
@ThomNichols Der apk/libTrick hat bei benutzerdefinierten Attributen mit Referenzformat aus einem Bibliotheksprojekt bei mir nicht funktioniert. Was tat der Arbeit Gebrauch war apk/res-auto, wie in vorgeschlagen stackoverflow.com/a/13420366/22904 knapp unter und auch in stackoverflow.com/a/10217752
Giulio Piancastelli

1
Zitieren von @Qberticus: "Flag-Attribute sind ähnlich, außer dass die Werte definiert werden müssen, damit sie zusammengebissen werden können". Meiner Meinung nach ist dies eine Art Untertreibung des Hauptunterschieds zwischen enumund flag: Ersteres lässt uns einen und nur einen Wert auswählen, letzteres lässt uns mehrere kombinieren. Ich schrieb eine längere Antwort auf eine ähnliche Frage auf , hier und jetzt diese Frage gefunden zu haben , ich dachte ich , dass verknüpfen würde.
Rad Haring

5
a.recycle()ist hier sehr wichtig, um Speicher
freizugeben

87

Qberticus 'Antwort ist gut, aber ein nützliches Detail fehlt. Wenn Sie diese in einer Bibliothek implementieren, ersetzen Sie:

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

mit:

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

Andernfalls weist die Anwendung, die die Bibliothek verwendet, Laufzeitfehler auf.


3
Dies wurde erst kürzlich hinzugefügt ... Ich denke, vor ein paar Wochen. Sicherlich wurde es hinzugefügt, lange nachdem Qberticus seine Antwort geschrieben hatte.
ArtOfWarfare

12
Ich denke, es ist älter als das, aber es wurde sicherlich hinzugefügt, lange nachdem Qberticus seine Antwort geschrieben hat. Ihm überhaupt nichts vorzuwerfen, nur ein nützliches Detail hinzuzufügen.
Neil Miller

11
Ich habe Qbericus 'Antwort aktualisiert, um apk / res-auto zu verwenden, um Verwirrung zu vermeiden.
Intrications

15

Die obige Antwort deckt alles sehr detailliert ab, abgesehen von ein paar Dingen.

Wenn keine Stile vorhanden sind, wird zunächst die (Context context, AttributeSet attrs)Methodensignatur verwendet, um die Voreinstellung zu instanziieren. In diesem Fall verwenden Sie einfach context.obtainStyledAttributes(attrs, R.styleable.MyCustomView), um das TypedArray abzurufen.

Zweitens wird nicht behandelt, wie mit plauralen Ressourcen (Mengenzeichenfolgen) umgegangen wird. Diese können mit TypedArray nicht behandelt werden. Hier ist ein Codeausschnitt aus meiner SeekBarPreference, der die Zusammenfassung der Präferenz festlegt und ihren Wert entsprechend dem Wert der Präferenz formatiert. Wenn die XML-Datei für die Voreinstellung android: summary auf eine Textzeichenfolge oder eine Zeichenfolge resouce setzt, wird der Wert der Voreinstellung in die Zeichenfolge formatiert (sie sollte% d enthalten, um den Wert abzurufen). Wenn android: summary auf eine plaurale Ressource festgelegt ist, wird dies zum Formatieren des Ergebnisses verwendet.

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

  • Dies ist nur ein Beispiel. Wenn Sie jedoch versucht sind, die Zusammenfassung auf dem Einstellungsbildschirm festzulegen, müssen Sie notifyChanged()die onDialogClosedMethode der Voreinstellung aufrufen .

5

Der traditionelle Ansatz ist voll von Boilerplate-Code und ungeschicktem Umgang mit Ressourcen. Deshalb habe ich das Spyglass-Framework erstellt . In diesem Beispiel wird gezeigt, wie eine benutzerdefinierte Ansicht erstellt wird, in der ein Zeichenfolgentitel angezeigt wird.

Schritt 1: Erstellen Sie eine benutzerdefinierte Ansichtsklasse.

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

Schritt 2: Definieren Sie ein Zeichenfolgenattribut in der values/attrs.xmlRessourcendatei:

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

Schritt 3: Wenden Sie die @StringHandlerAnmerkung auf die setTitleMethode an, um das Spyglass-Framework anzuweisen, den Attributwert an diese Methode weiterzuleiten, wenn die Ansicht aufgeblasen wird.

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

Nachdem Ihre Klasse über eine Spyglass-Annotation verfügt, erkennt das Spyglass-Framework diese zur Kompilierungszeit und generiert die CustomView_SpyglassCompanionKlasse automatisch .

Schritt 4: Verwenden Sie die generierte Klasse in der initMethode der benutzerdefinierten Ansicht :

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

Das ist es. Wenn Sie nun die Klasse aus XML instanziieren, interpretiert der Spyglass-Begleiter die Attribute und führt den erforderlichen Methodenaufruf aus. Wenn wir beispielsweise das folgende Layout aufblasen, setTitlewird dies "Hello, World!"als Argument aufgerufen .

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

Das Framework ist nicht auf Zeichenfolgenressourcen beschränkt und verfügt über viele verschiedene Anmerkungen für die Verarbeitung anderer Ressourcentypen. Es enthält auch Anmerkungen zum Definieren von Standardwerten und zum Übergeben von Platzhalterwerten, wenn Ihre Methoden mehrere Parameter haben.

Weitere Informationen und Beispiele finden Sie im Github-Repo.


Dasselbe können Sie mit Google Data Binding erreichen. Wenn für ein bestimmtes Attribut keine Attributbindung vorhanden ist, versucht GDB, die set * -Methode zu finden, und verwendet sie stattdessen. In diesem Fall müssten Sie beispielsweise schreiben android:title="@{&quot;Hello, world!&quot;}".
Spook

0

Wenn Sie das formatAttribut im attrElement weglassen , können Sie damit auf eine Klasse aus XML-Layouts verweisen.

  • Beispiel aus attrs.xml .
  • Android Studio versteht, dass auf die Klasse aus XML verwiesen wird
    • dh
      • Refactor > Rename funktioniert
      • Find Usages funktioniert
      • und so weiter...

Geben Sie kein formatAttribut in ... / src / main / res / values ​​/ attrs.xml an

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

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

</resources>

Verwenden Sie es in einer Layoutdatei ... / 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>

Analysieren Sie die Klasse in Ihrem Ansichtsinitialisierungscode ... / 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()
            }
        }
    }
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.