Deklarowanie niestandardowego elementu interfejsu użytkownika Androida za pomocą XML

Odpowiedzi:

840

Przewodnik dla programistów Androida zawiera sekcję Budowanie niestandardowych komponentów . Niestety, omówienie atrybutów XML obejmuje jedynie zadeklarowanie kontroli w pliku układu, a nie obsługę wartości wewnątrz inicjalizacji klasy. Kroki są następujące:

1. Zadeklaruj atrybuty w 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>

Zwróć uwagę na użycie nieoznaczonej nazwy w declare-styleabletagu. Niestandardowe atrybuty Androida, takie jak extraInformationmuszą mieć zadeklarowany typ. Tagi zadeklarowane w nadklasie będą dostępne w podklasach bez konieczności ponownego zgłaszania.

2. Utwórz konstruktory

Ponieważ istnieją dwa konstruktory, które używają AttributeSetdo inicjalizacji, wygodnie jest utworzyć osobną metodę inicjowania dla konstruktorów.

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.MyCustomViewjest automatycznie generowanym int[]zasobem, w którym każdy element jest identyfikatorem atrybutu. Atrybuty są generowane dla każdej właściwości w pliku XML poprzez dołączenie nazwy atrybutu do nazwy elementu. Na przykład R.styleable.MyCustomView_android_textzawiera android_textatrybut dla MyCustomView. Atrybuty można następnie pobrać z TypedArrayróżnych getfunkcji. Jeśli atrybut nie jest zdefiniowany w zdefiniowanym w XML, to nulljest zwracany. Z wyjątkiem oczywiście, jeśli typ zwracany jest prymitywem, w którym to przypadku zwracany jest drugi argument.

Jeśli nie chcesz odzyskać wszystkich atrybutów, możesz ręcznie utworzyć tę tablicę. Identyfikator standardowych atrybutów Androida jest zawarty android.R.attr, a atrybuty tego projektu są w R.attr.

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

Należy pamiętać, że należy nie używać niczego android.R.styleable, co za tym wątku może się zmienić w przyszłości. W dokumentacji nadal znajduje się przegląd wszystkich tych stałych w jednym miejscu.

3. Użyj go w plikach układu, takich jak layout\main.xml

Dołącz deklarację przestrzeni nazw xmlns:app="http://schemas.android.com/apk/res-auto"do elementu xml najwyższego poziomu. Przestrzenie nazw zapewniają metodę pozwalającą uniknąć konfliktów, które czasami występują, gdy różne schematy używają tych samych nazw elementów ( więcej informacji można znaleźć w tym artykule ). Adres URL to po prostu sposób jednoznacznej identyfikacji schematów - pod tym adresem URL nie trzeba nic hostować . Jeśli wydaje się, że to nic nie robi, to dlatego, że tak naprawdę nie trzeba dodawać prefiksu przestrzeni nazw, chyba że trzeba rozwiązać konflikt.

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

Odwołaj się do widoku niestandardowego, używając w pełni kwalifikowanej nazwy.

Próbka Android LabelView

Jeśli chcesz uzyskać pełny przykład, spójrz na przykładowy widok widoku etykiety Androida.

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

Jest to zawarte w LinearLayoutatrybucie z przestrzenią nazw:xmlns:app="http://schemas.android.com/apk/res-auto"

Spinki do mankietów

Casebash
źródło
14
Chciałbym dodać, że jeśli element główny wymaga niestandardowej przestrzeni nazw, konieczne będzie dodanie zarówno standardowej przestrzeni nazw Androida, jak i własnej niestandardowej, w przeciwnym razie mogą wystąpić błędy kompilacji.
Chase
11
Ta odpowiedź jest najczystszym zasobem w Internecie na temat niestandardowych parametrów XML, jaki udało mi się znaleźć. Dziękuję, Casebash.
Artem Russakovskii
2
z jakiegoś powodu edytor wizualny odmawia użycia zapisanej wartości tekstowej dla Androida: tekst, ale urządzenie dobrze z niej korzysta. dlaczego ?
deweloper Androida
2
@androiddeveloper Wygląda na to, że edytor Eclipse odmawia użycia wartości dla wszystkich atrybutów android: Chciałbym wiedzieć, czy jest to funkcja, czy błąd
deej
4
Jaki jest cel xmlns: przestrzeń nazw aplikacji i res-auto?
IgorGanapolsky
91

Świetne referencje. Dzięki! Dodatek do tego:

Jeśli zdarzy się, że dołączasz projekt biblioteki, który zadeklarował niestandardowe atrybuty widoku niestandardowego, musisz zadeklarować przestrzeń nazw projektu, a nie bibliotekę. Na przykład:

Biorąc pod uwagę, że biblioteka ma pakiet „com.example.library.customview”, a działający projekt ma pakiet „com.example.customview”, wówczas:

Nie będzie działać (pokazuje błąd „błąd: nie znaleziono identyfikatora zasobu dla atrybutu„ newAttr ”w pakiecie„ 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" />

Będzie działać:

<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
źródło
47
Zostało to ~ naprawione w podglądzie ADT 17. Aby skorzystać z przestrzeni nazw aplikacji z biblioteki, zadeklaruj xmlns:app="http://schemas.android.com/apk/res-auto"Patrz komentarz 57 w code.google.com/p/android/issues/detail?id=9656
nmr
2
Dołączenie niestandardowej przestrzeni nazw zwraca teraz błądSuspicious namespace: Did you mean http://schemas.android.com/apk/res-auto
Ben Wilkinson,
niestandardowa przestrzeń nazw kończy się na res-auto, ponieważ korzystamy z Android Studio i Gradle. W przeciwnym razie (np. Niektóre wersje Eclipse) zwykle kończy się na lib / [nazwa twojego pakietu]
Universe
niestandardowa przestrzeń nazw kończy się na, res-autoponieważ używamy Androida Studio i Gradle. W przeciwnym razie (np. Niektóre wersje Eclipse) zwykle kończy się lib/[your package name]. tj.http://schemas.android.com/apk/lib/[your package name]
Wszechświat
27

Dodatek do najczęściej głosowanej odpowiedzi.

uzyskaćStyledAttributes ()

Chcę dodać kilka słów na temat korzystania z metody replaceStyledAttributes () podczas tworzenia niestandardowego widoku przy użyciu wstępnie zdefiniowanych atrybutów Androida: xxx. Zwłaszcza, gdy korzystamy z TextAppearance.
Jak wspomniano w „2. Tworzenie konstruktorów”, widok niestandardowy pobiera AttributeSet podczas jego tworzenia. Główne zastosowanie możemy zobaczyć w kodzie źródłowym 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();

Co możemy tutaj zobaczyć?
obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes)
Zestaw atrybutów jest przetwarzany według kompozycji zgodnie z dokumentacją. Wartości atrybutów są kompilowane krok po kroku. Najpierw atrybuty są wypełniane z kompozycji, następnie wartości są zastępowane wartościami ze stylu, a na koniec dokładne wartości z XML dla specjalnej instancji widoku zastępują inne.
Tablica żądanych atrybutów - com.android.internal.R.styleable.TextView
jest to zwykła tablica stałych. Jeśli żądamy standardowych atrybutów, możemy zbudować tę tablicę ręcznie.

Czego nie wymieniono w dokumentacji - kolejność wyników elementów TypedArray.
Po zadeklarowaniu widoku niestandardowego w pliku attrs.xml generowane są specjalne stałe dla indeksów atrybutów. I możemy wyodrębnić Wartości w ten sposób: a.getString(R.styleable.MyCustomView_android_text). Ale do ręcznegoint[] nie ma stałych. Przypuszczam, że getXXXValue (arrayIndex) będzie działał dobrze.

Inne pytanie brzmi: „Jak możemy zastąpić stałe wewnętrzne i zażądać standardowych atrybutów?” Możemy użyć wartości android.R.attr. *.

Jeśli więc chcemy użyć standardowego atrybutu TextAppearance w widoku niestandardowym i odczytać jego wartości w konstruktorze, możemy zmodyfikować kod z TextView w ten sposób:

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

Gdzie zdefiniowano CustomLabel:

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

Może w jakiś sposób się mylę, ale dokumentacja Androida dotycząca metody receiveStyledAttributes () jest bardzo słaba.

Rozszerzanie standardowego komponentu interfejsu użytkownika

W tym samym czasie możemy po prostu rozszerzyć standardowy komponent interfejsu użytkownika, używając wszystkich jego zadeklarowanych atrybutów. To podejście nie jest tak dobre, ponieważ TextView na przykład deklaruje wiele właściwości. I nie będzie możliwe wdrożenie pełnej funkcjonalności w przesłoniętej funkcji onMeasure () i onDraw ().

Ale możemy poświęcić teoretycznie szerokie ponowne wykorzystanie niestandardowego komponentu. Powiedz „Wiem dokładnie, jakich funkcji będę używać” i nie udostępniaj nikomu kodu.

Następnie możemy zaimplementować konstruktor CustomComponent(Context, AttributeSet, defStyle). Po wywołaniu super(...)wszystkie atrybuty zostaną przeanalizowane i udostępnione za pomocą metod pobierających.

yuriy.weiss
źródło
czy predefiniowane atrybuty Androida: xxx działają w projektantach GUI Eclipse?
deej
Takie atrybuty są rozpoznawane przez wtyczkę Eclipse ADT w edytorze właściwości. Widzę wartości domyślne z mojego stylu, jeśli niektóre wartości nie są zdefiniowane. I nie zapomnij dodać adnotacji @RemoteView do swojej klasy.
yuriy.weiss
Nie mogę tego zrobić. Eclipse ciągle ładuje wartości zerowe dla getText i rzuca android.content.res.Resources $ NotFoundException dla getResourceId, chociaż aplikacja działa dobrze na urządzeniu.
deej
Przepraszam, nie mogę ci pomóc. Stworzyłem tylko projekt demonstracyjny w celu przetestowania możliwości i nie spotkałem się z takimi błędami.
yuriy.weiss
Jest to o wiele lepsze niż odwzorowywanie niestandardowych atrybutów niestandardowego widoku na wbudowane atrybuty wbudowanych widoków zawartych w nim.
samis
13

Wygląda na to, że Google zaktualizowało swoją stronę programisty i dodało tam różne szkolenia.

Jeden z nich dotyczy tworzenia niestandardowych widoków i można go znaleźć tutaj

mitch000001
źródło
5

Wielkie dzięki za pierwszą odpowiedź.

Jeśli chodzi o mnie, miałem tylko jeden problem. Podczas nadmuchiwania mojego widoku wystąpił błąd: java.lang.NoSuchMethodException: MyView (kontekst, atrybuty)

Rozwiązałem go, tworząc nowy konstruktor:

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

Mam nadzieję, że to pomoże!

użytkownik2346922
źródło
0

Możesz dołączyć dowolny plik układu do innego pliku układu jako-

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

tutaj pliki układu w tagu dołączania to inne pliki układu .xml w tym samym folderze res.

Akshay Paliwal
źródło
Próbowałem tego, mam problem z tym, że dołączony układ nie może być „dostosowany”, nie może tworzyć ogólnych. Na przykład, gdy dołączę przycisk w podobny sposób, jeśli spróbuję ustawić tekst w pliku XML, to zadziała.
cfl