Stilfähige Attribute in Android deklarieren

81

Es gibt nur wenige wertvolle Dokumentationen zu dem declare-styleableTag, mit denen wir benutzerdefinierte Stile für Komponenten deklarieren können. Ich habe diese Liste gültiger Werte für das formatAttribut des attrTags gefunden. Das ist zwar nett, erklärt aber nicht, wie man einige dieser Werte verwendet. Beim Durchsuchen von attr.xml (der Android-Quelle für die Standardattribute) habe ich festgestellt, dass Sie Folgendes tun können:

<!-- The most prominent text color.  -->
<attr name="textColorPrimary" format="reference|color" />

Das formatAttribut kann offensichtlich auf eine Kombination von Werten gesetzt werden. Vermutlich formathilft das Attribut dem Parser, einen tatsächlichen Stilwert zu interpretieren. Dann habe ich dies in attr.xml entdeckt:

<!-- Default text typeface. -->
<attr name="typeface">
    <enum name="normal" value="0" />
    <enum name="sans" value="1" />
    <enum name="serif" value="2" />
    <enum name="monospace" value="3" />
</attr>

<!-- Default text typeface style. -->
<attr name="textStyle">
    <flag name="normal" value="0" />
    <flag name="bold" value="1" />
    <flag name="italic" value="2" />
</attr>

Beide scheinen eine Reihe zulässiger Werte für den angegebenen Stil zu deklarieren.

Ich habe also zwei Fragen:

  1. Was ist der Unterschied zwischen einem Stilattribut, das einen Wertesatz annehmen kann, und einem, der einen Wertesatz enumannehmen kann flag?
  2. Kennt jemand eine bessere Dokumentation für die Funktionsweise declare-styleable(außer Reverse Engineering des Android-Quellcodes)?
Ted Hopp
quelle

Antworten:

72

Hier ist diese Frage: Definieren von benutzerdefinierten Attributen mit einigen Informationen, aber nicht viel.

Und dieser Beitrag . Es hat gute Informationen über Flaggen und Aufzählungen:

Benutzerdefinierte XML-Attributflags

Flags sind spezielle Attributtypen, da sie nur eine sehr kleine Teilmenge von Werten zulassen, nämlich diejenigen, die unter dem Attribut-Tag definiert sind. Flags werden durch ein Attribut "name" und ein Attribut "value" angegeben. Die Namen müssen innerhalb dieses Attributtyps eindeutig sein, die Werte jedoch nicht. Dies ist der Grund, warum wir während der Entwicklung der Android-Plattform "fill_parent" und "match_parent" hatten, die beide demselben Verhalten zugeordnet waren. Ihre Werte waren identisch.

Das Namensattribut wird dem Namen zugeordnet, der an der Wertstelle im Layout-XML verwendet wird, und erfordert kein Namespace-Präfix. Daher habe ich für den obigen "tilingMode" "center" als Attributwert gewählt. Ich hätte genauso gut "gestreckt" oder "wiederholen" wählen können, aber sonst nichts. Es wäre nicht einmal erlaubt gewesen, die tatsächlichen Werte zu ersetzen.

Das value-Attribut muss eine Ganzzahl sein. Die Wahl zwischen hexadezimaler oder standardmäßiger Zifferndarstellung liegt bei Ihnen. Es gibt einige Stellen im Android-Code, an denen beide verwendet werden, und der Android-Compiler akzeptiert diese auch gerne.

Benutzerdefinierte XML-Attribut-Aufzählungen

Aufzählungen werden fast identisch wie Flags mit einer Bestimmung verwendet. Sie können austauschbar mit ganzen Zahlen verwendet werden. Unter der Haube werden Aufzählungen und Ganzzahlen demselben Datentyp zugeordnet, nämlich einer Ganzzahl. Wenn Enums in der Attributdefinition mit Ganzzahlen angezeigt werden, verhindern sie „magische Zahlen“, die immer schlecht sind. Aus diesem Grund können Sie ein "android: layout_width" mit einer Dimension, einer Ganzzahl oder einer benannten Zeichenfolge "fill_parent" haben.

Nehmen wir an, ich erstelle ein benutzerdefiniertes Attribut mit dem Namen "layout_scroll_height", das entweder eine Ganzzahl oder eine Zeichenfolge "scroll_to_top" akzeptiert. Dazu würde ich ein Formatattribut "integer" hinzufügen und dem mit der Aufzählung folgen:

<attr name="layout_scroll_height" format="integer">  
    <enum name="scroll_to_top" value="-1"/> 
</attr>

Die einzige Bedingung bei der Verwendung von Enums auf diese Weise ist, dass ein Entwickler, der Ihre benutzerdefinierte Ansicht verwendet, den Wert "-1" absichtlich in die Layoutparameter einfügen kann. Dies würde die Sonderfalllogik von "scroll_to_top" auslösen. Solch ein unerwartetes (oder erwartetes) Verhalten könnte Ihre Bibliothek schnell in den Stapel "Legacy-Code" verschieben, wenn die Enum-Werte schlecht ausgewählt würden.


Aus meiner Sicht sind die tatsächlichen Werte, die Sie einem Attribut in der Realität hinzufügen können, durch das begrenzt, was Sie daraus erhalten können. Weitere Hinweise finden Sie in der AttributeSetKlassenreferenz hier .

Sie können erhalten:

  • Boolesche Werte ( getAttributeBooleanValue),
  • schwimmt ( getAttributeFloatValue),
  • ints ( getAttributeIntValue),
  • Ints (as getAttributeUnsignedIntValue),
  • und Strings ( getAttributeValue)
Aleadam
quelle
Danke für diese Links. Der statisch typisierte Blog ist besonders schön. Es ist nah genug an "echter Dokumentation", dass ich dies als gelöst markiere.
Ted Hopp
@ Ted in der Tat, dieser Beitrag hat tolle Infos. Jedenfalls würde ich mir über diese Aufzählungen keine Sorgen machen, es sei denn, Sie schreiben eine Bibliothek mit wiederverwendbaren Ansichten oder erweitern das Framework. Bools, Floats, Ints und Strings können Sie für reguläre benutzerdefinierte Ansichten weit bringen. Das ist natürlich, wenn die Frage nicht nur eine sehr gesunde Neugier befriedigen sollte :)
Aleadam
@Aleadam - Die Frage wurde durch eine echte Bewerbung motiviert. Ich habe benutzerdefinierte Attribute verwendet und musste ein neues hinzufügen. Es stellt sich heraus, dass das richtige Format für das neue Attribut eine Aufzählung ist, aber bis ich den von Ihnen angegebenen Link gelesen habe, konnte ich keine Informationen über den Unterschied zwischen der Verwendung von enumund finden flag.
Ted Hopp
@ Ted Ich bin froh, dass es nützlich war. Es gibt ein paar andere Beiträge in diesem Blog, die interessant erscheinen, obwohl ich sie nur durchblättere, hatte ich noch keine Zeit, sie zu lesen.
Aleadam
3
+1 für alle, die meinen Blog verlinken. Ich habe wirklich versucht zu analysieren, wofür jedes dieser Tags verwendet wurde. Es ist möglicherweise nicht vollständig und sollte möglicherweise im Laufe der Zeit aktualisiert werden, aber wenn jemand etwas hinzuzufügen hat, bin ich ganz Ohr.
Wheaties
70

Die Antwort von @Aleadam ist sehr hilfreich, aber imho lässt es einen großen Unterschied zwischen enumund aus flag. Ersteres soll einen und nur einen Wert auswählen, wenn wir das entsprechende Attribut für eine Ansicht zuweisen. Die Werte des letzteren können jedoch mit dem bitweisen ODER-Operator kombiniert werden.

Ein Beispiel in res/values/attr.xml

<!-- declare myenum attribute -->
<attr name="myenum">
    <enum name="zero" value="0" />
    <enum name="one" value="1" />
    <enum name="two" value="2" />
    <enum name="three" value="3" />
</attr>

<!-- declare myflags attribute -->
<attr name="myflags">
    <flag name="one" value="1" />
    <flag name="two" value="2" />
    <flag name="four" value="4" />
    <flag name="eight" value="8" />
</attr>

<!-- declare our custom widget to be styleable by these attributes -->
<declare-styleable name="com.example.MyWidget">
    <attr name="myenum" />
    <attr name="myflags" />
</declare-styleable>

In können res/layout/mylayout.xmlwir jetzt tun

<com.example.MyWidget
    myenum="two"
    myflags="one|two"
    ... />

Eine Aufzählung wählt also einen ihrer möglichen Werte aus, während Flags kombiniert werden können. Die numerischen Werte sollten diesen Unterschied widerspiegeln. In der Regel soll die Sequenz 0,1,2,3,...für Aufzählungen (z. B. als Array-Indizes) und Flags verwendet werden, 1,2,4,8,...damit sie unabhängig voneinander hinzugefügt oder entfernt werden können. Verwenden Sie dazu bitweises ODER |, um Flags zu kombinieren.

Wir könnten explizit "Meta-Flags" mit Werten definieren, die keine Zweierpotenz sind, und so eine Art Kurzform für gängige Kombinationen einführen. Zum Beispiel, wenn wir dies in unsere myflagsErklärung aufgenommen hätten

<flag name="three" value="3" />

dann hätten wir myflags="three"stattdessen myflags="one|two"für völlig identische Ergebnisse schreiben können wie 3 == 1|2.

Persönlich schließe ich immer gerne ein

<flag name="none" value="0" /> <!-- or "normal, "regular", and so on -->
<flag name="all" value="15" /> <!-- 15 == 1|2|4|8 -->

Dadurch kann ich alle Flags gleichzeitig deaktivieren oder setzen.

Subtiler kann es sein, dass ein Flag von einem anderen impliziert wird. Nehmen wir in unserem Beispiel an, dass das eightgesetzte Flag das Setzen des fourFlags erzwingen sollte (sofern dies nicht bereits geschehen ist). Wir könnten dann neu definieren, eightum sozusagen die fourFlagge vorab einzuschließen.

<flag name="eight" value="12" /> <!-- 12 == 8|4 -->

Wenn Sie die Attribute in einem Bibliotheksprojekt deklarieren, sie aber in Layouts eines anderen Projekts anwenden möchten (abhängig von der Bibliothek), müssen Sie ein Namespace-Präfix verwenden, das Sie im XML-Stammelement binden müssen. Z.B,

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:auto="http://schemas.android.com/apk/res-auto"
    ... >

    <com.example.MyWidget
        auto:myenum="two"
        auto:myflags="one|two"
        ... />

</RelativeLayout>
Rad Haring
quelle
Gute Argumente. Ich nehme an, dass für jemanden, der mit Aufzählungen aus Programmiersprachen vertraut ist, der Unterschied zwischen einer Flagge und einer Aufzählung eine zweite Natur wäre. (Ich habe nicht einmal bemerkt, dass der Punkt, den Sie angesprochen haben, in Aleadams Antwort fehlt.) Dies sind definitiv nützliche zusätzliche Informationen. +1
Ted Hopp
Diese Antwort zeigt deutlich, wie Aufzählungen von Flags unterschieden werden können, während benutzerdefinierte Attribute definiert werden. Es hat mir eindeutig geholfen, Flaggen zu verwenden :) +1
ptitvinou