Tworzenie komponentów widoku niestandardowego

Wypróbuj sposób tworzenia wiadomości
Jetpack Compose to zalecany zestaw narzędzi UI na Androida. Dowiedz się, jak korzystać z układów w funkcji Utwórz

Android oferuje zaawansowany i zaawansowany model składowy do tworzenia UI oparty na podstawowych klasach układu View i ViewGroup. Platforma zawiera różne gotowe podklasy View i ViewGroup (nazywane odpowiednio widżetami i układami), których możesz używać do tworzenia interfejsu użytkownika.

Częściowa lista dostępnych widżetów to Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner oraz bardziej specjalne AutoCompleteTextView, ImageSwitcher i TextSwitcher.

Dostępne układy to LinearLayout, FrameLayout, RelativeLayout i inne. Więcej przykładów znajdziesz w artykule Typowe układy.

Jeśli żaden z gotowych widżetów ani układów nie spełnia Twoich wymagań, możesz utworzyć własną podklasę View. Jeśli chcesz wprowadzić 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 podklas View daje Ci precyzyjną kontrolę nad wyglądem i funkcją elementu na ekranie. Aby zorientować się, jakie opcje są dostępne w widokach niestandardowych, oto kilka przykładów ich użycia:

  • Możesz utworzyć całkowicie renderowany typ View – na przykład pokrętło regulacji głośności renderowane za pomocą grafiki 2D, które przypomina analogowe sterowanie elektronicznym.
  • Możesz połączyć grupę komponentów View w jeden komponent, na przykład aby utworzyć pole kombi (kombinację wyskakującej listy i pola tekstowego do swobodnego wpisywania), panel z podwójnym panelem (lewy i prawy panel z listą w każdym z nich, w którym możesz zmienić przypisanie elementu, który znajduje się na danej liście) itd.
  • Możesz zastąpić sposób renderowania komponentu EditText na ekranie. Przykładowa aplikacja NotePad wykorzystuje to rozwiązanie do tworzenia stron notatnika w linii.
  • 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 wyjaśniamy, jak tworzyć widoki niestandardowe i używać ich w swojej aplikacji. Szczegółowe informacje znajdziesz w klasie View.

Podstawowe podejście

Oto ogólny przegląd informacji, które pozwolą Ci tworzyć własne komponenty View:

  1. Rozszerz istniejącą klasę lub podklasę View o własne zajęcia.
  2. Zastąp niektóre metody z klasy nadrzędnej. Metody zastępowania klas nadrzędnych zaczynają się od on, np. onDraw(), onMeasure() i onKeyDown(). Jest to podobne do zdarzeń on w Activity lub ListActivity, które zastępujesz dla cyklu życia i innych punktów zaczepienia funkcjonalności.
  3. Użyj nowej klasy rozszerzenia. Po zakończeniu możesz użyć nowej klasy rozszerzenia zamiast widoku, na podstawie którego zostało utworzone.

W pełni dostosowane komponenty

Możesz tworzyć w pełni dostosowane komponenty graficzne, które będą się wyświetlać w dowolny sposób. Może Ci się przydać graficzny miernik VU, który wygląda jak stary wskaźnik analogowy, albo widok tekstowy do śpiewania, w którym odbijająca się piłka porusza się wzdłuż słów, gdy śpiewasz razem z urządzeniem do karaoke. Możesz potrzebować czegoś, czego nie mogą robić wbudowane komponenty, niezależnie od tego, jak je połączysz.

Na szczęście możesz tworzyć komponenty, które wyglądają i działają tak, jak chcesz, a ograniczenia ogranicza jedynie Twoja wyobraźnia, rozmiar ekranu i dostępna moc obliczeniowa. Mając na uwadze, że Twoja aplikacja będzie musiała uruchomić coś o znacznie niższej mocy niż komputer stacjonarny.

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 go w celu utworzenia nowego superkomponentu.
  • Możesz dostarczyć konstruktor, który może pobierać atrybuty i parametry z kodu XML oraz korzystać z własnych atrybutów i parametrów, takich jak kolor i zakres miernika VU lub szerokość i tłumienie igły.
  • Prawdopodobnie zechcesz utworzyć własne detektory zdarzeń, akcesory właściwości i modyfikatory, a także bardziej zaawansowane zachowanie w klasie komponentu.
  • Prawie na pewno chcesz zastąpić działanie onMeasure(), a konieczne jest zastąpienie onDraw(), jeśli chcesz, by komponent się wyświetlał. Choć w obu przypadkach działa domyślny, onDraw() nie ma żadnego działania, a domyślny onMeasure() zawsze ustawia rozmiar 100 x 100, czego prawdopodobnie nie chcesz.
  • W razie potrzeby możesz też zastąpić inne metody on.

Rozszerzaj funkcje onDraw() i onMeasure()

Metoda onDraw() udostępnia obiekt Canvas, w którym możesz zastosować dowolne elementy: grafikę 2D, inne standardowe lub niestandardowe komponenty, styl tekstu i cokolwiek innego, co przychodzi Ci do głowy.

onMeasure() bardziej się angażuje. onMeasure() to kluczowy element umowy renderowania między komponentem a jego kontenerem. Trzeba zastąpić zmienną onMeasure(), aby skutecznie i dokładnie raportować pomiary jej składowych. Jest to trochę bardziej skomplikowane ze względu na wymagania dotyczące limitów ze strony nadrzędnej, które są przekazywane do metody onMeasure(), oraz przez wymóg wywołania metody setMeasuredDimension() z mierzoną szerokością i wysokością po ich obliczeniu. Jeśli nie wywołasz tej metody z zastąpionej metody onMeasure(), spowoduje to wystąpienie wyjątku w czasie pomiaru.

Ogólnie wdrożenie onMeasure() wygląda tak:

  • Zastąpiona metoda onMeasure() jest wywoływana ze specyfikacją szerokości i wysokości, co jest traktowane jako wymagania dotyczące ograniczeń tworzonych przez Ciebie pomiarów szerokości i wysokości. Parametry widthMeasureSpec i heightMeasureSpec to kody całkowite reprezentujące wymiary. Pełne informacje na temat ograniczeń, jakie mogą być wymagane przez te specyfikacje, znajdziesz w dokumentacji referencyjnej w sekcji View.onMeasure(int, int)Ta dokumentacja dotyczy również całego działania pomiaru.
  • Metoda onMeasure() komponentu oblicza szerokość i wysokość, które są wymagane do renderowania komponentu. Musi ona przestrzegać podanych specyfikacji, ale może przekroczyć podane wymagania. W takim przypadku rodzic może wybrać, co ma zrobić, np. przyciąć, przewinąć, zgłosić wyjątek lub poprosić onMeasure() o ponowną próbę pomiaru.
  • Po obliczeniu szerokości i wysokości wywołaj z obliczonymi pomiarami metodę setMeasuredDimension(int width, int height). Jeśli tego nie zrobisz, zostanie utworzony wyjątek.

Oto podsumowanie innych standardowych metod, które platforma wywołuje w przypadku wyświetleń:

Kategoria Metody Opis
na podstawie trendów Konstruktorki Istnieje forma konstruktora, która jest wywoływana, gdy widok jest tworzony na podstawie kodu, oraz formularz, który jest wywoływany, gdy widok zostanie wyświetlony w pliku układu. Drugi formularz analizuje i stosuje atrybuty zdefiniowane w pliku układu.
onFinishInflate() Wywoływana po wyświetleniu i wszystkich jego elementach podrzędnych są powiększone z pliku XML.
Układ onMeasure(int, int) Wywoływana w celu określenia wymagań dotyczących rozmiaru dla tego widoku i wszystkich jego elementów podrzędnych.
onLayout(boolean, int, int, int, int) Wywoływana, gdy ten widok musi przypisać rozmiar i pozycję wszystkim jego elementom podrzędnym.
onSizeChanged(int, int, int, int) Wywoływana po zmianie rozmiaru tego widoku.
Rysunek onDraw(Canvas) Wywoływane, gdy widok musi wyrenderować swoją treść.
Przetwarzanie zdarzeń onKeyDown(int, KeyEvent) Wywoływana po wystąpieniu zdarzenia wyłączenia.
onKeyUp(int, KeyEvent) Wywoływana po wystąpieniu kluczowego zdarzenia.
onTrackballEvent(MotionEvent) Wywoływane po wystąpieniu zdarzenia ruchu kulki.
onTouchEvent(MotionEvent) Wywoływane po wystąpieniu zdarzenia ruchu na ekranie dotykowym.
główny temat filmu, onFocusChanged(boolean, int, Rect) Wywoływane, gdy widok zyskuje lub traci ostrość.
onWindowFocusChanged(boolean) Wywoływane, gdy okno zawierające widok zyskuje lub traci fokus.
Dołączanie onAttachedToWindow() Wywoływane, gdy widok jest dołączony do okna.
onDetachedFromWindow() Wywoływane, gdy widok zostaje odłączony od jego okna.
onWindowVisibilityChanged(int) Wywoływana po zmianie widoczności okna zawierającego widok.

Złożone elementy sterujące

Jeśli nie chcesz tworzyć całkowicie niestandardowego komponentu, ale zamiast tego potrzebujesz wielokrotnego użytku, który składa się z grupy istniejących elementów sterujących, najlepszym rozwiązaniem będzie utworzenie komponentu złożonego (lub elementu sterującego). Podsumowując, gromadzi to wiele bardziej szczegółowych ustawień lub widoków w logiczną grupę elementów, które można traktować jak jedną rzecz. Pole kombi może na przykład składać się z jednowierszowego pola EditText i przyległego przycisku z dołączoną listą. Jeśli użytkownik kliknie przycisk i wybierze pozycję z listy, pole EditText zostanie wypełnione, ale może też wpisać coś bezpośrednio w polu EditText.

Na Androidzie łatwo dostępne są 2 inne widoki: Spinner i AutoCompleteTextView. Tak czy inaczej, dobrym przykładem jest koncepcja pola kombi.

Aby utworzyć komponent złożony, wykonaj te czynności:

  • Podobnie jak w przypadku Activity, użyj metody deklaratywnej (opartej na XML), aby utworzyć zawarte komponenty, lub zagnieźdź je automatycznie z poziomu kodu. Punktem początkowym jest zwykle jakiś element Layout, więc utwórz klasę, która będzie rozszerzać tabelę Layout. W przypadku pola złożonego możesz użyć pola LinearLayout w orientacji poziomej. Możesz zagnieżdżać w środku inne układy, więc komponent złożony może być dowolnie złożony i uporządkowany.
  • W konstruktorze nowej klasy wybierz parametry, których oczekuje superklasa, i przekaż je najpierw do konstruktora klasy nadrzędnej. Następnie możesz skonfigurować pozostałe widoki do użycia w nowym komponencie. W tym miejscu utworzysz pole EditText i wyskakujące okienko. Możesz wprowadzić do pliku XML własne atrybuty i parametry, które może pobrać i wykorzystać Twój konstruktor.
  • Opcjonalnie możesz utworzyć detektory zdarzeń, które mogą generować zawarte widoki. Przykładem jest metoda odbiornika kliknięć elementu listy, która aktualizuje zawartość pola EditText po wybraniu listy.
  • Opcjonalnie możesz utworzyć własne właściwości z akcesorami i modyfikatorami. Możesz na przykład ustawić wartość EditText na początku w komponencie i w razie potrzeby wysłać zapytanie o jego zawartość.
  • Opcjonalnie zastąp onDraw() i onMeasure(). Zwykle nie jest to konieczne przy rozszerzaniu obiektu Layout, ponieważ układ ma zachowanie domyślne, które prawdopodobnie działa dobrze.
  • Opcjonalnie możesz zastąpić inne metody on, takie jak onKeyDown(), aby na przykład wybrać określone wartości domyślne z wyskakującej listy pola złożonego po naciśnięciu określonego klawisza.

Używanie obiektu Layout jako podstawy niestandardowego elementu sterującego ma wiele zalet, w tym:

  • Możesz określić układ za pomocą plików deklaratywnych XML, tak jak w przypadku ekranu aktywności, lub możesz automatycznie tworzyć widoki i zagnieżdżać je w układzie z poziomu kodu.
  • Metody onDraw() i onMeasure() oraz większość pozostałych metod on dział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 stanowiły jeden komponent.

Modyfikowanie dotychczasowego typu widoku

Jeśli znajdziesz komponent, który działa podobnie do Twojego, możesz go rozszerzyć i zastąpić działanie, które chcesz zmienić. Korzystając z w pełni dostosowanego komponentu, możesz wykonywać wszystkie te same czynności, co w przypadku tego komponentu, ale zaczynając od bardziej specjalistycznej klasy w hierarchii View, możesz bezpłatnie uzyskać działanie, które spełni Twoje oczekiwania.

Na przykład przykładowa aplikacja NotePad pokazuje wiele aspektów korzystania z platformy Androida. Wśród nich jest rozszerzenie widoku EditText w celu utworzenia notatnika z linią. Nie jest to doskonały przykład, a interfejsy API mogą się zmieniać, ale pokazują zasady.

Zaimportuj przykładowy plik z NotePad do Android Studio lub sprawdź źródło, korzystając z podanego linku. W szczególności zobacz definicję właściwości LinedEditText w pliku NoteEditor.java.

Oto kilka rzeczy, które warto wziąć pod uwagę w tym pliku:

  1. Definicja

    Klasa jest zdefiniowana w tym wierszu:
    public static class LinedEditText extends EditText

    LinedEditText jest zdefiniowaną jako klasa wewnętrzna w aktywności NoteEditor, ale jest publiczna, więc można do niej uzyskać dostęp jako NoteEditor.LinedEditText spoza klasy NoteEditor.

    Poza tym LinedEditText to static, co oznacza, że nie generuje tak zwanych „metod syntetycznych”, które mogłyby uzyskać dostęp do danych z klasy nadrzędnej. Oznacza to, że działa ona jako osobna klasa, a nie coś ściśle powiązanego z NoteEditor. Jest to bardziej przejrzysty sposób tworzenia klas wewnętrznych, jeśli nie potrzebują one dostępu do stanu z klasy zewnętrznej. Utrzymuje wygenerowaną klasę niewielkiego rozmiaru i pozwala na łatwe korzystanie z niej z innych klas.

    LinedEditText rozszerza widok EditText, który w tym przypadku należy dostosować. Gdy skończysz, nowa klasa może zastąpić normalny widok EditText.

  2. Inicjowanie klasy

    Jak zawsze, pierwszy z nich jest super. Nie jest to konstrukt domyślny, ale z parametrami. Obiekt EditText jest tworzony z tymi parametrami, gdy jest powiększony z pliku układu XML. Konstruktor musi je więc pobrać i przekazać do konstruktora klasy nadrzędnej.

  3. Zastąpione metody

    Ten przykład zastępuje tylko metodę onDraw(), ale podczas tworzenia własnych komponentów niestandardowych może być konieczne zastąpienie innych.

    W tym przykładzie zastąpienie metody onDraw() pozwala pomalować niebieskie linie w obszarze roboczym widoku EditText. Obszar roboczy jest przekazywany do zastąpionej metody onDraw(). Metoda super.onDraw() jest wywoływana przed zakończeniem. Należy wywołać metodę klasy nadrzędnej. W tym przypadku wywołaj go na końcu po pomalowaniu wierszy, które chcesz uwzględnić.

  4. Komponent niestandardowy

    Masz już komponent niestandardowy, ale jak go wykorzystać? W przykładzie NotePad komponent niestandardowy jest używany bezpośrednio z układu deklaratywnego, więc spójrz na obiekt note_editor.xml w folderze res/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 w pliku XML jako widok ogólny, a klasa określa się za pomocą pełnego pakietu. Do zdefiniowanej przez Ciebie klasy wewnętrznej odwołuje się zapis NoteEditor$LinedEditText, który jest standardowym sposobem odwoływania się do wewnętrznych klas w języku programowania Java.

    Jeśli komponent widoku niestandardowego nie jest zdefiniowany jako klasa wewnętrzna, możesz zadeklarować komponent widoku danych nazwą elementu XML i wykluczyć atrybut class. Na przykład:

    <com.example.android.notepad.LinedEditText
      id="@+id/note"
      ... />
    

    Zwróć uwagę, że klasa LinedEditText jest teraz oddzielnym plikiem klas. Jeśli klasa jest umieszczona w klasie NoteEditor, ta metoda nie działa.

    Pozostałe atrybuty i parametry w definicji to atrybuty przekazywane do konstruktora komponentu niestandardowego, a potem przekazywane do konstruktora EditText, więc są to te same parametry, co w widoku EditText. Możesz też dodać własne parametry.

Tworzenie komponentów niestandardowych jest tak skomplikowane, jak to konieczne.

Bardziej zaawansowany komponent może zastąpić jeszcze więcej metod on i wprowadzić własne metody pomocnicze, znacząco dostosowując właściwości i działanie. Jedynym ograniczeniem jest wyobraźnia i potrzeby komponentu.