Układy w widokach
Układ określa strukturę interfejsu użytkownika aplikacji, np. w aktywności. Wszystkie elementy układu są tworzone na podstawie hierarchii obiektów View
i ViewGroup
. View
zwykle rysuje coś, co użytkownik widzi i z czym może wchodzić w interakcje. ViewGroup
to niewidoczny kontener, który określa strukturę układu dla obiektu View
i innych obiektów ViewGroup
, jak widać na ilustracji 1.
Obiekty View
są często nazywane widżetami i mogą być jedną z wielu podklas, takich jak Button
lub TextView
. Obiekty ViewGroup
są zwykle nazywane układami i mogą być jednym z wielu typów obiektów mających odrębną strukturę układu, np. LinearLayout
lub ConstraintLayout
.
Układ możesz zadeklarować na 2 sposoby:
- Deklaruj elementy interfejsu w języku XML. Android zapewnia prosty słownik XML odpowiadający klasom i podklasom
View
, takim jak widżety i układy. Możesz też skorzystać z edytora układu w Android Studio, by utworzyć układ XML przy użyciu interfejsu typu „przeciągnij i upuść”. - Inicjuj elementy układu w czasie działania. Twoja aplikacja może w sposób zautomatyzowany tworzyć obiekty
View
iViewGroup
oraz modyfikować ich właściwości.
Zadeklarowanie interfejsu w formacie XML umożliwia oddzielenie prezentacji aplikacji od kodu, który kontroluje jej działanie. Korzystanie z plików XML ułatwia też tworzenie różnych układów w zależności od rozmiaru i orientacji ekranu. Zostało to szczegółowo opisane w artykule Obsługa różnych rozmiarów ekranów.
Platforma Androida daje Ci możliwość korzystania z jednej lub obu tych metod do tworzenia interfejsu aplikacji. Możesz na przykład zadeklarować domyślne układy aplikacji w formacie XML, a następnie zmodyfikować układ w czasie działania.
Napisz kod XML
Korzystając ze słownika XML na Androidzie, możesz szybko projektować układy interfejsu i zawarte w nich elementy ekranu, tak samo jak strony internetowe w kodzie HTML zawierają serię zagnieżdżonych elementów.
Każdy plik układu musi zawierać dokładnie 1 element główny, który musi być obiektem View
lub ViewGroup
. Po zdefiniowaniu elementu głównego możesz dodawać kolejne obiekty układu lub widżety jako elementy podrzędne, aby stopniowo tworzyć hierarchię View
definiującą układ. Oto na przykład układ XML, w którym pionowa konstrukcja LinearLayout
przechowuje TextView
i Button
:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello, I am a Button" /> </LinearLayout>
Gdy zadeklarujesz układ w formacie XML, zapisz plik z rozszerzeniem .xml
w katalogu res/layout/
projektu na Androida, aby kompilacja przebiegła prawidłowo.
Więcej informacji o składni pliku XML układu znajdziesz w sekcji Zasób Układ.
Wczytaj zasób XML
Podczas kompilowania aplikacji każdy plik układu XML jest kompilowany w zasób View
. Wczytaj zasób układu w implementacji wywołania zwrotnego Activity.onCreate()
aplikacji. Aby to zrobić, wywołaj właściwość setContentView()
i przekaż jej odniesienie do zasobu układu w formularzu: R.layout.layout_file_name
. Jeśli np. układ XML został zapisany jako main_layout.xml
, wczytaj go na urządzeniu Activity
w ten sposób:
Kotlin
fun onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) setContentView(R.layout.main_layout) }
Java
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); }
Platforma Androida wywołuje metodę wywołania zwrotnego onCreate()
w Activity
po uruchomieniu Activity
. Więcej informacji o cyklach życia działań znajdziesz w artykule Wprowadzenie do aktywności.
Atrybuty
Każdy obiekt View
i ViewGroup
obsługuje własne różne atrybuty XML. Niektóre atrybuty są specyficzne dla obiektu View
. Na przykład TextView
obsługuje atrybut textSize
. Te atrybuty są jednak również dziedziczone przez wszystkie obiekty View
, które rozszerzają tę klasę. Niektóre z nich są wspólne dla wszystkich obiektów View
, ponieważ są dziedziczone z głównej klasy View
, np. w atrybucie id
. Inne atrybuty są uważane za parametry układu, czyli atrybuty opisujące określone orientacje układu obiektu View
określone przez nadrzędny obiekt ViewGroup
tego obiektu.
ID
Z każdym obiektem View
może być powiązany identyfikator w postaci liczby całkowitej, który umożliwia jednoznaczne zidentyfikowanie obiektu View
w drzewie. Podczas kompilowania aplikacji identyfikator jest przywoływany w postaci liczby całkowitej, ale jest on zwykle przypisywany w pliku XML układu jako ciąg znaków w atrybucie id
. To atrybut XML wspólny dla wszystkich obiektów View
definiowany przez klasę View
. Używasz go bardzo często. Składnia identyfikatora w tagu XML jest taka:
android:id="@+id/my_button"
Symbol at (@) na początku ciągu oznacza, że parser XML analizuje i rozwija pozostałą część ciągu identyfikatora oraz identyfikuje go jako zasób identyfikatora. Symbol plusa (+) oznacza nową nazwę zasobu, którą należy utworzyć i dodać do zasobów w pliku R.java
.
Platforma Androida udostępnia wiele innych zasobów identyfikatorów. Gdy odwołujesz się do identyfikatora zasobu Androida, nie potrzebujesz symbolu plusa, ale musisz dodać przestrzeń nazw pakietu android
w ten sposób:
android:id="@android:id/empty"
Przestrzeń nazw pakietu android
wskazuje, że odwołujesz się do identyfikatora z klasy zasobów android.R
, a nie do klasy zasobów lokalnych.
Aby tworzyć widoki danych i odwoływać się do nich z poziomu aplikacji, możesz użyć takiego wzorca:
- Zdefiniuj widok w pliku układu i przypisz mu unikalny identyfikator, jak w tym przykładzie:
<Button android:id="@+id/my_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/my_button_text"/>
- Utwórz instancję obiektu widoku i przechwyć ją z układu, zwykle za pomocą metody
onCreate()
, jak w tym przykładzie:Kotlin
val myButton: Button = findViewById(R.id.my_button)
Java
Button myButton = (Button) findViewById(R.id.my_button);
Podczas tworzenia obiektu RelativeLayout
ważne jest określenie identyfikatorów obiektów widoków.
W układzie względnym widoki równorzędne mogą definiować układ w stosunku do innego, do którego odwołuje się unikalny identyfikator.
Identyfikator nie musi być unikalny w całym drzewie, ale musi być niepowtarzalny w obrębie przeszukiwanej części drzewa. Często może to być całe drzewo, więc w miarę możliwości zadbaj o to, aby było unikalne.
Parametry układu
Atrybuty układu XML o nazwie layout_something
określają parametry układu dla elementu View
, które są odpowiednie dla elementu ViewGroup
, w którym się on znajduje.
Każda klasa ViewGroup
implementuje zagnieżdżoną klasę rozszerzającą zakres ViewGroup.LayoutParams
.
Ta podklasa zawiera typy właściwości, które określają rozmiar i pozycję każdego widoku podrzędnego odpowiednio do danej grupy widoków. Jak widać na rys. 2, nadrzędna grupa widoków określa parametry układu dla każdego widoku podrzędnego, w tym dla podrzędnego widoku danych.
Każda podklasa LayoutParams
ma własną składnię wartości ustawień. Każdy element podrzędny musi definiować element LayoutParams
odpowiedni dla elementu nadrzędnego, ale może też określać inny element LayoutParams
dla własnych elementów podrzędnych.
Wszystkie grupy widoków danych mogą zawierać szerokość i wysokość za pomocą elementów layout_width
i layout_height
, a każdy widok musi mieć ich definiowanie. Wiele LayoutParams
ma opcjonalne marginesy i obramowania.
Możesz określić szerokość i wysokość za pomocą dokładnych pomiarów, ale może to być niezbyt częste. Częściej do ustawiania szerokości lub wysokości używasz jednej z tych stałych:
wrap_content
: informuje widok, że wyświetla się do wymiarów wymaganych przez jego zawartość.match_parent
: informuje, że widok danych jest większy, na tyle, na ile zezwala jego nadrzędna grupa widoków.
Ogólnie nie zalecamy określania szerokości i wysokości układu za pomocą jednostek bezwzględnych takich jak piksele. Lepszym sposobem jest stosowanie pomiarów względnych, takich jak niezależne jednostki pikseli (dp), wrap_content
lub match_parent
, ponieważ pomagają one prawidłowo wyświetlać się na ekranach urządzeń o różnych rozmiarach. Akceptowane typy pomiarów są określone w sekcji Zasób Układ.
Pozycja układu
Widok ma prostokątną geometrię. Zawiera on lokalizację wyrażoną jako parę współrzędnych left i top oraz dwa wymiary wyrażone jako szerokość i wysokość. Jednostkami lokalizacji i wymiarów jest piksel.
Lokalizację widoku możesz pobrać, wywołując metody getLeft()
i getTop()
.
Pierwsze zwraca współrzędną lewą (x) prostokąta reprezentującego widok. Zwraca ona współrzędną górną (y) prostokąta reprezentującego widok. Zwracają one lokalizację widoku w odniesieniu do jego elementu nadrzędnego. Gdy np. getLeft()
zwraca 20, oznacza to, że widok znajduje się 20 pikseli po prawej stronie lewej krawędzi jego bezpośredniego elementu nadrzędnego.
Istnieją też wygodne metody, które pozwalają uniknąć niepotrzebnych obliczeń: getRight()
i getBottom()
.
Zwracają one współrzędne prawej i dolnej krawędzi prostokąta reprezentującego widok. Na przykład wywołanie metody getRight()
przebiega podobnie do tego obliczenia: getLeft() + getWidth()
.
Rozmiar, dopełnienie i marginesy
Rozmiar widoku jest wyrażany przez szerokość i wysokość. Widok ma dwie pary wartości szerokości i wysokości.
Pierwsza para jest znana jako szerokość mierzona i mierzona wysokość. Określają one, jak duży ma być dany widok w ramach swojego elementu nadrzędnego. Zmierzone wymiary możesz uzyskać, wywołując metodę getMeasuredWidth()
i getMeasuredHeight()
.
Druga para to szerokość i wysokość lub czasami szerokość rysowania i wysokość rysowania. Określają one rzeczywisty rozmiar widoku na ekranie, w czasie rysowania i po układzie. Te wartości mogą, ale nie muszą, różnić się od zmierzonej szerokości i wysokości. Szerokość i wysokość możesz sprawdzić, wywołując metodę getWidth()
i getHeight()
.
Podczas pomiaru wymiarów widoku uwzględnia się jego dopełnienie. Dopełnienie jest wyrażone w pikselach dla lewej, górnej, prawej i dolnej części widoku.
Możesz użyć dopełnienia, aby odsunąć zawartość widoku o określoną liczbę pikseli. Na przykład 2 lewe dopełnienie przesuwa zawartość widoku o 2 piksele na prawo od lewej krawędzi. Dopełnienie możesz ustawić za pomocą metody setPadding(int, int, int, int)
i zapytać je, wywołując getPaddingLeft()
, getPaddingTop()
, getPaddingRight()
i getPaddingBottom()
.
Widok może definiować dopełnienie, ale nie obsługuje marginesów. ale grupy widoków obsługują marże. Więcej informacji znajdziesz w opisie ViewGroup
i ViewGroup.MarginLayoutParams
.
Więcej informacji o wymiarach znajdziesz w artykule Wymiar.
Poza automatycznym ustawianiem marginesów i dopełnienia możesz je też ustawiać w układach XML, jak w tym przykładzie:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="16dp" android:padding="8dp" android:text="Hello, I am a TextView" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:paddingBottom="4dp" android:paddingEnd="8dp" android:paddingStart="8dp" android:paddingTop="4dp" android:text="Hello, I am a Button" /> </LinearLayout>
Poprzedni przykład przedstawia stosowane marginesy i dopełnienie. Element TextView
ma równomierne marginesy i dopełnienie dookoła, a Button
pokazuje, jak możesz je osobno stosować na różnych krawędziach.
Typowe układy
Każda podklasa klasy ViewGroup
zapewnia unikalny sposób wyświetlania zagnieżdżonych w niej widoków. Najbardziej elastycznym typem układu, który zapewnia najlepsze narzędzia do utrzymywania niewielkiej hierarchii układu, jest ConstraintLayout
.
Oto kilka typowych typów układów wbudowanych w Androida.
Porządkuje elementy podrzędne w jeden poziomy lub pionowy wiersz i tworzy pasek przewijania, jeśli długość okna przekracza długość ekranu.
Tworzenie list dynamicznych
Jeśli treść układu jest dynamiczna lub nie została wstępnie określona, możesz użyć właściwości RecyclerView
lub podklasy AdapterView
.
RecyclerView
jest zasadniczo lepszą opcją, ponieważ wykorzystuje pamięć bardziej wydajnie niż AdapterView
.
Typowe układy możliwe w przypadku elementów RecyclerView
i AdapterView
to:
RecyclerView
udostępnia więcej możliwości i opcję tworzenia niestandardowego menedżera układu.
Wypełnianie widoku adaptera danymi
Możesz wypełnić AdapterView
, np. ListView
lub GridView
, powiążąc instancję AdapterView
z elementem Adapter
, co spowoduje pobieranie danych ze źródła zewnętrznego i utworzenie View
reprezentującego każdy wpis danych.
Android udostępnia kilka podklas funkcji Adapter
, które przydają się do pobierania różnego rodzaju danych i tworzenia widoków dla interfejsu AdapterView
. Dwa najpopularniejsze adaptery to:
ArrayAdapter
- Używaj tego adaptera, gdy źródłem danych jest tablica. Domyślnie
ArrayAdapter
tworzy widok dla każdego elementu tablicy, wywołująctoString()
w każdym elemencie i umieszczając jego zawartość wTextView
.Jeśli np. masz tablicę ciągów znaków, które chcesz wyświetlić w elemencie
ListView
, zainicjuj nowy obiektArrayAdapter
za pomocą konstruktora, aby określić układ dla każdego ciągu i tablicy ciągów:Kotlin
val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
Java
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray);
Argumenty tego konstruktora są następujące:
- Twoja aplikacja
Context
- Układ, który zawiera
TextView
dla każdego ciągu znaków w tablicy - Tablica ciągu znaków
Następnie wywołaj
setAdapter()
na urządzeniuListView
:Kotlin
val listView: ListView = findViewById(R.id.listview) listView.adapter = adapter
Java
ListView listView = (ListView) findViewById(R.id.listview); listView.setAdapter(adapter);
Aby dostosować wygląd każdego elementu, możesz zastąpić metodę
toString()
w przypadku obiektów w tablicy. Możesz też utworzyć widok dla każdego elementu innego niżTextView
(jeśli na przykład potrzebujesz klasyImageView
dla każdego elementu tablicy), rozszerz klasęArrayAdapter
i zastąpgetView()
, aby zwracać odpowiedni typ widoku dla każdego elementu. - Twoja aplikacja
SimpleCursorAdapter
- Używaj tego adaptera, gdy dane pochodzą z urządzenia
Cursor
. Jeśli używasz właściwościSimpleCursorAdapter
, określ układ, w jakim mają być używane poszczególne wiersze w tabeliCursor
, oraz kolumny z tabeliCursor
, które chcesz wstawić w widokach wybranego układu. Jeśli na przykład chcesz utworzyć listę imion i nazwisk oraz numerów telefonów, możesz wykonać zapytanie, które zwróci wartośćCursor
zawierającą wiersz dla każdej osoby oraz kolumny z imionami i nazwiskami oraz numerami. Następnie utworzysz tablicę ciągu znaków, określającą kolumny ze zbioruCursor
, które mają się znaleźć w układzie każdego wyniku, oraz tablicę liczby całkowitej wskazującą widoki, w których należy umieścić każdą kolumnę:Kotlin
val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER) val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
Java
String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, ContactsContract.CommonDataKinds.Phone.NUMBER}; int[] toViews = {R.id.display_name, R.id.phone_number};
Gdy tworzysz wystąpienie elementu
SimpleCursorAdapter
, przekaż układ, którego chcesz użyć w przypadku każdego wyniku, obiektCursor
zawierający wyniki oraz te 2 tablice:Kotlin
val adapter = SimpleCursorAdapter(this, R.layout.person_name_and_number, cursor, fromColumns, toViews, 0) val listView = getListView() listView.adapter = adapter
Java
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, R.layout.person_name_and_number, cursor, fromColumns, toViews, 0); ListView listView = getListView(); listView.setAdapter(adapter);
SimpleCursorAdapter
tworzy widok dla każdego wiersza wCursor
przy użyciu podanego układu, wstawiając każdy elementfromColumns
do odpowiedniego widokutoViews
.
Jeśli w trakcie działania aplikacji zmienisz dane źródłowe odczytywane przez adapter, wywołaj notifyDataSetChanged()
.
Informuje to załączony widok o zmianie danych i odświeża się automatycznie.
Obsługa zdarzeń kliknięcia
Możesz reagować na zdarzenia kliknięcia każdego elementu w elemencie AdapterView
, implementując interfejs AdapterView.OnItemClickListener
. Na przykład:
Kotlin
listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id -> // Do something in response to the click. }
Java
// Create a message handling object as an anonymous class. private OnItemClickListener messageClickedHandler = new OnItemClickListener() { public void onItemClick(AdapterView parent, View v, int position, long id) { // Do something in response to the click. } }; listView.setOnItemClickListener(messageClickedHandler);
Dodatkowe materiały
Zobacz, jak używane są układy, w aplikacji demonstracyjnej Sunflower na GitHubie.