Android oferuje zaawansowany i wydajny model komponentowy do tworzenia interfejsu użytkownika oparty na podstawowych klasach układu View i ViewGroup. Platforma zawiera różne gotowe podklasy View i ViewGroup, zwane odpowiednio widżetami i układami, których możesz używać do tworzenia interfejsu.
Częściowa lista dostępnych widżetów obejmuje Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner oraz bardziej specjalistyczne AutoCompleteTextView, ImageSwitcher i TextSwitcher.
Dostępne układy to m.in. LinearLayout, FrameLayout, RelativeLayout i inne. Więcej przykładów znajdziesz w sekcji Typowe układy.
Jeśli żaden z gotowych widżetów ani układów nie spełnia Twoich potrzeb, możesz utworzyć własną Viewpodklasę. Jeśli chcesz wprowadzić tylko niewielkie zmiany w istniejącym widżecie lub układzie, możesz utworzyć podklasę widżetu lub układu i zastąpić jego metody.
Tworzenie własnych View podklas daje precyzyjną kontrolę nad wyglądem i funkcją elementu ekranu. Aby dać Ci wyobrażenie o tym, jaką kontrolę zapewniają widoki niestandardowe, podajemy kilka przykładów ich zastosowań:
-
Możesz utworzyć w pełni niestandardowy typ
View, np. pokrętło „głośności”, renderowane za pomocą grafiki 2D, które przypomina analogowe sterowanie elektroniczne. -
Możesz połączyć grupę komponentów
Vieww jeden nowy komponent, np. aby utworzyć pole kombi (połączenie listy wyskakującej i pola tekstowego do wpisywania dowolnych danych), dwupanelowy selektor (lewy i prawy panel z listą w każdym z nich, w którym możesz ponownie przypisać element do listy) itp. -
Możesz zastąpić sposób renderowania komponentu
EditTextna ekranie. Przykładowa aplikacja NotePad wykorzystuje to do tworzenia strony notatnika w linie. - Możesz rejestrować inne zdarzenia, np. naciśnięcia klawiszy, i obsługiwać je w niestandardowy sposób, np. w grze.
W sekcjach poniżej znajdziesz informacje o tym, jak tworzyć widoki niestandardowe i używać ich w aplikacji. Szczegółowe informacje znajdziesz w dokumentacji klasy View.
Podstawowe podejście
Oto ogólny przegląd informacji, które musisz znać, aby tworzyć własne Viewkomponenty:
-
Rozszerz istniejącą klasę
Viewlub podklasę o własną klasę. -
Zastąp niektóre metody z klasy nadrzędnej. Metody klasy nadrzędnej do zastąpienia zaczynają się od
on, np.onDraw(),onMeasure()ionKeyDown(). Jest to podobne do zdarzeńonwActivitylubListActivity, które zastępujesz w przypadku cyklu życia i innych funkcji. - Użyj nowej klasy rozszerzenia. Po zakończeniu tego procesu możesz używać nowej klasy rozszerzenia zamiast widoku, na którym była oparta.
W pełni dostosowane komponenty
Możesz tworzyć w pełni dostosowane komponenty graficzne, które będą wyświetlane w dowolny sposób. Możesz na przykład wybrać graficzny miernik VU, który wygląda jak stary analogowy wskaźnik, lub widok tekstu do śpiewania, w którym skacząca piłka przesuwa się wzdłuż słów, gdy śpiewasz z maszyną do karaoke. Może się zdarzyć, że potrzebujesz czegoś, czego nie da się uzyskać za pomocą wbudowanych komponentów, niezależnie od tego, jak je połączysz.
Na szczęście możesz tworzyć komponenty, które wyglądają i działają w dowolny sposób. Ogranicza Cię tylko wyobraźnia, rozmiar ekranu i dostępna moc obliczeniowa. Pamiętaj, że aplikacja może działać na urządzeniu o znacznie mniejszej mocy niż stacja robocza.
Aby utworzyć w pełni dostosowany komponent, weź pod uwagę te kwestie:
-
Najbardziej ogólnym widokiem, który możesz rozszerzyć, jest
View, więc zwykle zaczynasz od rozszerzenia tego widoku, aby utworzyć nowy superkomponent. - Możesz podać konstruktor, który może pobierać atrybuty i parametry z pliku XML, a także korzystać z własnych atrybutów i parametrów, takich jak kolor i zakres miernika VU czy szerokość i tłumienie igły.
- Prawdopodobnie będziesz też chcieć utworzyć własne detektory zdarzeń, akcesory właściwości i modyfikatory, a także bardziej zaawansowane zachowania w klasie komponentu.
-
Prawie na pewno chcesz zastąpić
onMeasure(), a jeśli chcesz, aby komponent coś wyświetlał, prawdopodobnie musisz też zastąpićonDraw(). Oba mają domyślne działanie, ale domyślny elementonDraw()nic nie robi, a domyślny elementonMeasure()zawsze ustawia rozmiar 100x100, co prawdopodobnie nie jest pożądane. -
W razie potrzeby możesz też zastąpić inne metody
on.
Rozszerzanie metod onDraw() i onMeasure()
Metoda onDraw() dostarcza element Canvas, na którym możesz zaimplementować wszystko, co chcesz: grafikę 2D, inne standardowe lub niestandardowe komponenty, sformatowany tekst lub cokolwiek innego, co przyjdzie Ci do głowy.
onMeasure() jest nieco bardziej skomplikowane. onMeasure() to kluczowy element umowy renderowania między komponentem a jego kontenerem. onMeasure() musi zostać zastąpiona, aby skutecznie i dokładnie raportować pomiary zawartych w niej części. Jest to nieco bardziej skomplikowane ze względu na wymagania dotyczące limitów ze strony elementu nadrzędnego, które są przekazywane do metody onMeasure(), oraz ze względu na konieczność wywołania metody setMeasuredDimension() z obliczoną szerokością i wysokością. Jeśli nie wywołasz tej metody z zastąpionej metody onMeasure(), w momencie pomiaru wystąpi wyjątek.
Ogólnie implementacja onMeasure() wygląda mniej więcej tak:
-
Zastąpiona metoda
onMeasure()jest wywoływana ze specyfikacjami szerokości i wysokości, które są traktowane jako wymagania dotyczące ograniczeń pomiarów szerokości i wysokości. ParametrywidthMeasureSpeciheightMeasureSpecto kody całkowite reprezentujące wymiary. Pełne odniesienie do rodzaju ograniczeń, które mogą być wymagane przez te specyfikacje, można znaleźć w dokumentacji referencyjnej w sekcjiView.onMeasure(int, int). Dokumentacja referencyjna wyjaśnia też całą operację pomiarową. -
Metoda
onMeasure()komponentu oblicza szerokość i wysokość pomiaru, które są wymagane do renderowania komponentu. Musi próbować zachować zgodność z przekazanymi specyfikacjami, ale może je przekraczać. W takim przypadku rodzic może podjąć decyzję, co zrobić, np. przyciąć, przewinąć, zgłosić wyjątek lub poprosićonMeasure()o ponowną próbę, być może z innymi specyfikacjami pomiaru. -
Po obliczeniu szerokości i wysokości wywołaj metodę
setMeasuredDimension(int width, int height)z obliczonymi wymiarami. Jeśli tego nie zrobisz, wystąpi wyjątek.
Oto podsumowanie innych standardowych metod, które framework wywołuje w przypadku widoków:
| Kategoria | Metody | Opis |
|---|---|---|
| Utworzenie | Zespoły | Istnieje forma konstruktora, która jest wywoływana, gdy widok jest tworzony z kodu, oraz forma, która jest wywoływana, gdy widok jest rozwijany z pliku układu. Druga forma analizuje i stosuje atrybuty zdefiniowane w pliku układu. |
|
Wywoływana po utworzeniu widoku i wszystkich jego elementów podrzędnych z pliku XML. | |
| Układ | |
Wywoływana w celu określenia wymagań dotyczących rozmiaru tego widoku i wszystkich jego elementów podrzędnych. |
|
Wywoływana, gdy ten widok musi przypisać rozmiar i pozycję do wszystkich swoich elementów podrzędnych. | |
|
Wywoływana, gdy zmieni się rozmiar tego widoku. | |
| Rysunek | |
Wywoływana, gdy widok musi renderować swoją zawartość. |
| Przetwarzanie zdarzeń | |
Wywoływana, gdy wystąpi zdarzenie naciśnięcia klawisza. |
|
Wywoływana, gdy wystąpi zdarzenie zwolnienia klawisza. | |
|
Wywoływana, gdy wystąpi zdarzenie ruchu trackballa. | |
|
Wywoływana, gdy wystąpi zdarzenie ruchu na ekranie dotykowym. | |
| Koncentracja | |
Wywoływana, gdy widok zyskuje lub traci fokus. |
|
Wywoływana, gdy okno zawierające widok zyskuje lub traci fokus. | |
| Dołączanie | |
Wywoływana, gdy widok jest dołączony do okna. |
|
Wywoływana, gdy widok jest odłączony od okna. | |
|
Wywoływana, gdy zmienia się widoczność okna zawierającego widok. |
Złożone elementy sterujące
Jeśli nie chcesz tworzyć w pełni dostosowanego komponentu, ale chcesz utworzyć komponent wielokrotnego użytku składający się z grupy istniejących elementów sterujących, najlepszym rozwiązaniem może być utworzenie komponentu złożonego (lub złożonego elementu sterującego). Podsumowując, łączy to kilka bardziej szczegółowych elementów sterujących lub widoków w logiczną grupę elementów, które można traktować jako jedną całość.
Na przykład pole kombi może być połączeniem pola EditText jedno wierszowego i sąsiadującego z nim przycisku z dołączoną listą wyskakującą. Jeśli użytkownik kliknie przycisk i wybierze coś z listy, pole EditText zostanie wypełnione, ale w razie potrzeby może też wpisać coś bezpośrednio w polu EditText.
W Androidzie są dostępne 2 inne widoki, które umożliwiają to działanie: Spinner i AutoCompleteTextView. Niezależnie od tego ta koncepcja pola kombi jest dobrym przykładem.
Aby utworzyć komponent złożony:
-
Podobnie jak w przypadku
Activity, możesz użyć podejścia deklaratywnego (opartego na XML) do tworzenia komponentów zawartych lub zagnieżdżać je programowo w kodzie. Zwykle punktem wyjścia jest jakiśLayout, więc utwórz klasę, która rozszerzaLayout. W przypadku pola wyboru możesz użyćLinearLayouto orientacji poziomej. Możesz zagnieżdżać w nim inne układy, dzięki czemu komponent złożony może być dowolnie złożony i strukturalny. -
W konstruktorze nowej klasy przyjmij wszystkie parametry, których oczekuje nadklasa, i przekaż je najpierw do konstruktora nadklasy. Następnie możesz skonfigurować inne widoki do użycia w nowym komponencie. W tym miejscu utworzysz pole
EditTexti listę wyskakującą. Możesz wprowadzić do pliku XML własne atrybuty i parametry, które konstruktor może pobrać i wykorzystać. -
Opcjonalnie możesz utworzyć detektory zdarzeń, które mogą generować widoki zawarte. Przykładem jest metoda detektora kliknięcia elementu listy, która aktualizuje zawartość elementu
EditTextpo dokonaniu wyboru na liście. -
Opcjonalnie możesz utworzyć własne właściwości z funkcjami dostępu i modyfikatorami. Na przykład ustaw wartość
EditTextpoczątkowo w komponencie i w razie potrzeby wysyłaj zapytania o jego zawartość. -
Opcjonalnie możesz zastąpić wartości
onDraw()ionMeasure(). Zwykle nie jest to konieczne w przypadku rozszerzaniaLayout, ponieważ układ ma domyślne działanie, które prawdopodobnie będzie odpowiednie. -
Opcjonalnie możesz zastąpić inne metody
on, np.onKeyDown(), aby wybrać określone wartości domyślne z listy wyskakującej pola kombi po naciśnięciu określonego klawisza.
Używanie elementu Layout jako podstawy niestandardowego komponentu ma wiele zalet, m.in.:
- Układ możesz określić za pomocą deklaratywnych plików XML, tak jak w przypadku ekranu aktywności, lub utworzyć widoki programowo i zagnieździć je w układzie z poziomu kodu.
-
Metody
onDraw()ionMeasure()oraz większość innych metodondziałają prawidłowo, więc nie musisz ich zastępować. - Możesz szybko tworzyć dowolnie złożone widoki złożone i używać ich ponownie tak, jakby były pojedynczym komponentem.
Modyfikowanie dotychczasowego typu widoku
Jeśli istnieje komponent podobny do tego, którego potrzebujesz, możesz go rozszerzyć i zastąpić zachowanie, które chcesz zmienić. Możesz robić wszystko to, co w przypadku w pełni dostosowanego komponentu, ale zaczynając od bardziej wyspecjalizowanej klasy w hierarchii View, możesz bezpłatnie uzyskać zachowanie, które Cię interesuje.
Na przykład aplikacja przykładowa NotePad pokazuje wiele aspektów korzystania z platformy Android. Jedną z nich jest rozszerzenie widoku, aby utworzyć notatnik w linie.EditText To nie jest idealny przykład, a interfejsy API do tego celu mogą się zmienić, ale pokazuje on zasady działania.
Jeśli jeszcze tego nie zrobiono, zaimportuj przykładową aplikację NotePad do Androida Studio lub wyświetl kod źródłowy, korzystając z podanego linku. W szczególności zapoznaj się z definicją LinedEditText w pliku NoteEditor.java.
Oto kilka kwestii, na które warto zwrócić uwagę w tym pliku:
-
Definicja
Klasa jest zdefiniowana za pomocą tego wiersza:
public static class LinedEditText extends EditTextLinedEditTextjest zdefiniowana jako klasa wewnętrzna w aktywnościNoteEditor, ale jest publiczna, więc można do niej uzyskać dostęp jakoNoteEditor.LinedEditTextspoza klasyNoteEditor.Poza tym
LinedEditTextjeststatic, co oznacza, że nie generuje tzw. „metod syntetycznych”, które umożliwiają dostęp do danych z klasy nadrzędnej. Oznacza to, że zachowuje się jak osobna klasa, a nie coś ściśle powiązanego zNoteEditor. Jest to prostszy sposób tworzenia klas wewnętrznych, jeśli nie potrzebują one dostępu do stanu klasy zewnętrznej. Dzięki temu wygenerowana klasa jest mała i można jej łatwo używać w innych klasach.LinedEditTextrozszerzaEditText, czyli widok, który należy w tym przypadku dostosować. Gdy skończysz, nowe zajęcia mogą zastąpić normalny widokEditText. -
Inicjowanie klasy
Jak zawsze najpierw wywoływana jest funkcja super. Nie jest to konstruktor domyślny, ale konstruktor sparametryzowany. Element
EditTextjest tworzony z tymi parametrami, gdy jest rozwijany z pliku układu XML. Dlatego konstruktor musi je przyjmować i przekazywać do konstruktora klasy nadrzędnej. -
Zastąpione metody
W tym przykładzie zastąpiona jest tylko metoda
onDraw(), ale podczas tworzenia własnych komponentów niestandardowych może być konieczne zastąpienie innych metod.W tym przykładzie zastąpienie metody
onDraw()umożliwia rysowanie niebieskich linii naEditText. Obiekt Canvas jest przekazywany do zastąpionej metodyonDraw(). Metodasuper.onDraw()jest wywoływana przed zakończeniem metody. Metoda klasy nadrzędnej musi zostać wywołana. W takim przypadku wywołaj go na końcu po narysowaniu linii, które chcesz uwzględnić. -
Komponent niestandardowy
Masz już komponent niestandardowy, ale jak go używać? W przykładzie NotePad komponent niestandardowy jest używany bezpośrednio z układu deklaratywnego, więc zajrzyj do
note_editor.xmlw folderzeres/layout:<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
Komponent niestandardowy jest tworzony jako widok ogólny w XML, a klasa jest określana za pomocą pełnego pakietu. Zdefiniowana klasa wewnętrzna jest przywoływana za pomocą notacji
NoteEditor$LinedEditText, która jest standardowym sposobem odwoływania się do klas wewnętrznych w języku programowania Java.Jeśli komponent widoku niestandardowego nie jest zdefiniowany jako klasa wewnętrzna, możesz zadeklarować komponent widoku za pomocą nazwy elementu XML i wykluczyć atrybut
class. Na przykład:<com.example.android.notepad.LinedEditText id="@+id/note" ... />
Zwróć uwagę, że klasa
LinedEditTextjest teraz osobnym plikiem klasy. Jeśli klasa jest zagnieżdżona w klasieNoteEditor, ta technika nie działa.Pozostałe atrybuty i parametry w definicji to te, które są przekazywane do konstruktora komponentu niestandardowego, a następnie do konstruktora
EditText, więc są to te same parametry, których używasz w przypadku widokuEditText. Możesz też dodawać własne parametry.
Tworzenie komponentów niestandardowych jest tak skomplikowane, jak tego potrzebujesz.
Bardziej zaawansowany komponent może zastąpić jeszcze więcej on metod i wprowadzić własne metody pomocnicze, znacznie dostosowując swoje właściwości i działanie. Ogranicza Cię tylko wyobraźnia i to, do czego ma służyć komponent.