Deklarowanie atrybutów stylizowanych w systemie Android

81

Istnieje niewielka dokumentacja dotycząca declare-styleabletagu, za pomocą której możemy zadeklarować niestandardowe style komponentów. Znalazłem listę prawidłowych wartości formatatrybutu attrtagu. Chociaż jest to miłe, jeśli chodzi o to, nie wyjaśnia, jak używać niektórych z tych wartości. Przeglądając attr.xml (źródło Androida dla standardowych atrybutów), odkryłem, że możesz robić takie rzeczy, jak:

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

formatAtrybut można oczywiście ustawić do kombinacji wartości. Przypuszczalnie formatatrybut pomaga parserowi zinterpretować rzeczywistą wartość stylu. Potem odkryłem to w attr.xml:

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

Oba wydają się deklarować zestaw dozwolonych wartości dla wskazanego stylu.

Mam więc dwa pytania:

  1. Jaka jest różnica między atrybutem stylu, który może przyjąć jeden z zestawu enumwartości, a takim, który może przyjąć zestaw flagwartości?
  2. Czy ktoś zna lepszą dokumentację dotyczącą tego, jak declare-styleabledziała (poza inżynierią wsteczną kodu źródłowego Androida)?
Ted Hopp
źródło

Odpowiedzi:

74

Oto pytanie: Definiowanie niestandardowych atrybutów z pewnymi informacjami, ale niewiele.

I ten post . Zawiera dobre informacje o flagach i wyliczeniach:

Niestandardowe flagi atrybutów XML

Flagi są specjalnymi typami atrybutów, ponieważ dopuszczają tylko bardzo mały podzbiór wartości, a mianowicie te, które są zdefiniowane pod znacznikiem atrybutu. Flagi są określone przez atrybut „nazwa” i atrybut „wartość”. Nazwy muszą być unikalne w ramach tego typu atrybutu, ale wartości nie muszą. To jest powód, dla którego podczas ewolucji platformy Android mieliśmy zarówno „fill_parent”, jak i „match_parent” odwzorowujące to samo zachowanie. Ich wartości były identyczne.

Atrybut nazwy jest odwzorowywany na nazwę używaną w miejscu wartości w kodzie XML układu i nie wymaga przedrostka przestrzeni nazw. Dlatego dla powyższego „tilingMode” wybrałem „center” jako wartość atrybutu. Mogłem równie łatwo wybrać „rozciągnięte” lub „powtarzające się”, ale nic poza tym. Nawet podstawianie wartości rzeczywistych nie byłoby dozwolone.

Atrybut value musi być liczbą całkowitą. Wybór szesnastkowej lub standardowej reprezentacji liczbowej należy do Ciebie. W kodzie Androida jest kilka miejsc, w których oba są używane, a kompilator Androida z przyjemnością je zaakceptuje.

Niestandardowe wyliczenia atrybutów XML

Wyliczenia są używane w prawie identyczny sposób jak flagi z jednym warunkiem, mogą być używane zamiennie z liczbami całkowitymi. Pod maską Wyliczenia i liczby całkowite są mapowane na ten sam typ danych, a mianowicie na liczbę całkowitą. Występując w definicji atrybutu z liczbami całkowitymi, wyliczenia służą do zapobiegania „magicznym liczbom”, które są zawsze złe. Dlatego możesz mieć „android: layout_width” z wymiarem, liczbą całkowitą lub nazwanym ciągiem „fill_parent”.

Aby umieścić to w kontekście, załóżmy, że tworzę niestandardowy atrybut o nazwie „layout_scroll_height”, który akceptuje liczbę całkowitą lub ciąg „scroll_to_top”. Aby to zrobić, dodałbym atrybut formatu „integer”, a następnie wyliczenie:

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

Jedynym warunkiem korzystania z wyliczeń w ten sposób jest to, że programista korzystający z niestandardowego widoku może celowo umieścić wartość „-1 ″ w parametrach układu. Spowoduje to uruchomienie logiki przypadku specjalnego „scroll_to_top”. Takie nieoczekiwane (lub oczekiwane) zachowanie może szybko przenieść Twoją bibliotekę na stos „starego kodu”, jeśli wartości Enum zostały wybrane źle.


Jak widzę, rzeczywiste wartości, które możesz w rzeczywistości dodać do atrybutu, są ograniczone przez to, co możesz z niego uzyskać. Sprawdź AttributeSetodniesienie klasy tutaj po więcej wskazówek.

Możesz otrzymać:

  • booleans ( getAttributeBooleanValue),
  • pływa ( getAttributeFloatValue),
  • ints ( getAttributeIntValue),
  • ints (as getAttributeUnsignedIntValue),
  • i ciągi ( getAttributeValue)
Aleadam
źródło
Dzięki za te linki. Szczególnie fajny jest blog z typowaniem statycznym. Jest wystarczająco blisko „prawdziwej dokumentacji”, że oznaczam to jako rozwiązane.
Ted Hopp
@Ted rzeczywiście, ten post zawiera świetne informacje. W każdym razie, chyba że piszesz bibliotekę widoków wielokrotnego użytku lub rozszerzasz framework, nie martwiłbym się o te wyliczenia. Wartości logiczne, zmiennoprzecinkowe, wartości typu int i ciągi znaków mogą zajść daleko w przypadku zwykłych niestandardowych widoków. To znaczy oczywiście, gdyby chodziło nie tylko o zaspokojenie bardzo zdrowej ciekawości :)
Aleadam
@Aleadam - Pytanie było motywowane prawdziwą aplikacją. Używałem atrybutów niestandardowych i musiałem dodać nowy. Okazuje się, że właściwym formatem nowego atrybutu jest wyliczenie, ale dopóki nie przeczytałem podanego przez Ciebie linku, nie mogłem znaleźć żadnych informacji na temat różnicy między użyciem enuma flag.
Ted Hopp
@Ted Cieszę się, że to było przydatne. Na tym blogu jest kilka innych postów, które wydają się interesujące, chociaż tylko je przeglądam, nie miałem jeszcze czasu, aby je przeczytać.
Aleadam
3
+1 dla każdego, kto linkuje do mojego bloga. Naprawdę starałem się przeanalizować, do czego służył każdy z tych tagów. To może nie być kompletne i być może powinno być aktualizowane w miarę upływu czasu, ale jeśli ktoś ma coś do dodania, mam wszystkie uszy.
pszenicy
70

Odpowiedź @Aleadam jest bardzo pomocna, ale imho pomija jedną zasadniczą różnicę między enumi flag. Pierwsza z nich ma na celu wybranie jednej i tylko jednej wartości, gdy przypisujemy odpowiedni atrybut do jakiegoś widoku. Te ostatnie wartości można jednak łączyć za pomocą bitowego operatora OR.

Przykład w 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>

W res/layout/mylayout.xmlmożemy teraz zrobić

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

Zatem wyliczenie wybiera jedną z możliwych wartości, podczas gdy flagi można łączyć. Wartości liczbowe powinny odzwierciedlać tę różnicę, zazwyczaj będziesz chciał, aby sekwencja przeszła 0,1,2,3,...dla wyliczeń (na przykład do użycia jako indeksy tablic) i flagi, aby 1,2,4,8,...mogły być niezależnie dodawane lub usuwane, używając bitowego LUB |do łączenia flag.

Moglibyśmy wyraźnie zdefiniować „meta flagi” z wartościami, które nie są potęgą 2, i w ten sposób wprowadzić rodzaj skrótu dla typowych kombinacji. Na przykład, gdybyśmy zawarli to w naszej myflagsdeklaracji

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

wtedy moglibyśmy myflags="three"zamiast tego napisać myflags="one|two", dla zupełnie identycznych wyników jak 3 == 1|2.

Osobiście lubię zawsze dołączać

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

co pozwoli mi usunąć lub ustawić wszystkie flagi jednocześnie.

Bardziej subtelnie może się zdarzyć, że jedna flaga jest implikowana przez inną. Zatem w naszym przykładzie załóżmy, eightże ustawiana flaga powinna wymuszać ustawienie fourflagi (jeśli jeszcze nie było). Moglibyśmy wtedy ponownie zdefiniować, eightaby wstępnie uwzględnić fourflagę,

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

Wreszcie, jeśli deklarujesz atrybuty w projekcie biblioteki, ale chcesz zastosować je w układach innego projektu (zależnego od biblioteki), musisz użyć przedrostka przestrzeni nazw, który musisz powiązać w głównym elemencie XML. Na przykład,

<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
źródło
Słuszne uwagi. Przypuszczam, że dla kogoś zaznajomionego z wyliczeniami z języków programowania różnica między flagą a wyliczeniem byłaby drugą naturą. (Nawet nie zdawałem sobie sprawy, że w odpowiedzi Aleadama brakowało tego, co mówiłeś). To jest zdecydowanie przydatna dodatkowa informacja. +1
Ted Hopp
Ta odpowiedź wyraźnie wskazuje, jak odróżnić wyliczenia od flag podczas definiowania atrybutów niestandardowych. Wyraźnie pomogło mi to przy użyciu flag :) +1
ptitvinou