Układy w widokach

Wypróbuj metodę Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak pracować z układami w Compose.

Układ określa strukturę interfejsu użytkownika w aplikacji, np. w aktywności. Wszystkie elementy układu są tworzone przy użyciu hierarchii obiektów View i ViewGroup. View zwykle rysuje coś, co użytkownik może zobaczyć i z czym może wejść w interakcję. ViewGroup to niewidoczny kontener, który określa strukturę układu dla View i innych obiektów ViewGroup, jak pokazano na rysunku 1.

Rysunek 1. Ilustracja hierarchii widoków, która określa układ interfejsu.

View są często nazywane widgetami i mogą należeć do wielu podklas, np. Button lub TextView. Obiekty ViewGroup są zwykle nazywane układami i mogą być jednego z wielu typów, które zapewniają inną strukturę układu, np. LinearLayout lub ConstraintLayout.

Układ możesz zadeklarować na 2 sposoby:

  • Deklarowanie elementów interfejsu w XML Android udostępnia prosty słownik XML, który odpowiada klasom i podklasom View, takim jak widżety i układy. Możesz też użyć edytora układu w Androidzie Studio, aby utworzyć układ XML za pomocą interfejsu „przeciągnij i upuść”.

  • Tworzenie instancji elementów układu w czasie działania Aplikacja może tworzyć obiekty ViewViewGroup oraz programowo manipulować ich właściwościami.

Deklarowanie interfejsu w XML-u pozwala oddzielić prezentację aplikacji od kodu, który kontroluje jej działanie. Korzystanie z plików XML ułatwia też udostępnianie różnych układów dla różnych rozmiarów ekranu i orientacji. Więcej informacji znajdziesz w artykule Obsługa różnych rozmiarów ekranu.

Platforma Androida umożliwia elastyczne korzystanie z obu tych metod lub z jednej z nich podczas tworzenia interfejsu aplikacji. Możesz na przykład zadeklarować domyślne układy aplikacji w formacie XML, a potem modyfikować je w czasie działania.

Napisz kod XML

Korzystając ze słownictwa XML Androida, możesz szybko projektować układy interfejsu i zawarte w nich elementy ekranu w taki sam sposób, w jaki tworzysz strony internetowe w HTML-u za pomocą serii 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ć Viewhierarchię definiującą układ. Na przykład ten układ XML używa pionowego elementu LinearLayout do przechowywania elementów TextViewButton:

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

Po zadeklarowaniu układu w XML zapisz plik z rozszerzeniem .xml w katalogu res/layout/ projektu na Androida, aby można go było prawidłowo skompilować.

Więcej informacji o składni pliku XML układu znajdziesz w artykule Zasób układu.

Wczytywanie zasobu XML

Podczas kompilowania aplikacji każdy plik układu XML jest kompilowany do zasobu View. Załaduj zasób układu w implementacji wywołania zwrotnego w aplikacji.Activity.onCreate() Aby to zrobić, wywołaj metodę setContentView(), przekazując do niej odwołanie do zasobu układu w formie R.layout.layout_file_name. Jeśli na przykład układ XML jest zapisany jako main_layout.xml, załaduj go dla elementu 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 Android wywołuje metodę wywołania zwrotnego onCreate()Activity, gdy uruchamia się Activity. Więcej informacji o cyklach życia aktywności znajdziesz w artykule Wprowadzenie do aktywności.

Atrybuty

Każdy obiekt ViewViewGroup obsługuje własny zestaw atrybutów XML. Niektóre atrybuty są specyficzne dla obiektu View. Na przykład TextView obsługuje atrybut textSize. Te atrybuty są jednak dziedziczone przez wszystkie View obiekty, które rozszerzają tę klasę. Niektóre są wspólne dla wszystkich obiektów View, ponieważ są dziedziczone z klasy głównej View, np. atrybut id. Pozostałe atrybuty są traktowane jako parametry układu, czyli atrybuty opisujące określone orientacje układu obiektu View, zdefiniowane przez obiekt nadrzędny ViewGroup.

ID

Z każdym obiektem View może być powiązany identyfikator w postaci liczby całkowitej, który jednoznacznie identyfikuje obiekt View w drzewie. Podczas kompilacji aplikacji ten identyfikator jest traktowany jako liczba całkowita, ale zwykle jest przypisywany w pliku XML układu jako ciąg znaków w atrybucie id. Jest to atrybut XML wspólny dla wszystkich obiektów View, który jest zdefiniowany przez klasę View. używasz go bardzo często; Składnia identyfikatora w tagu XML jest następująca:

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 rozpoznaje go jako zasób identyfikatora. Symbol plusa (+) oznacza, że jest to nowa nazwa zasobu, którą należy utworzyć i dodać do zasobów w pliku R.java.

Framework Androida oferuje wiele innych zasobów identyfikatorów. Podczas odwoływania się do identyfikatora zasobu Androida nie musisz używać symbolu plusa, ale musisz dodać android przestrzeń nazw pakietu 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 z lokalnej klasy zasobów.

Aby utworzyć widoki i odwoływać się do nich z aplikacji, możesz użyć typowego wzorca w ten sposób:

  1. 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"/>
  2. Utwórz instancję obiektu widoku i przechwyć ją z układu, zwykle w metodzie onCreate(), jak pokazano w tym przykładzie:

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)

    Java

    Button myButton = (Button) findViewById(R.id.my_button);

Określanie identyfikatorów obiektów widoku jest ważne podczas tworzenia RelativeLayout. W układzie względnym widoki równorzędne mogą definiować swój układ względem innego widoku równorzędnego, do którego odwołują się za pomocą unikalnego identyfikatora.

Identyfikator nie musi być unikalny w całym drzewie, ale musi być unikalny w części drzewa, w której szukasz. Często może to być całe drzewo, więc w miarę możliwości warto zadbać o jego unikalność.

Parametry układu

Atrybuty układu XML o nazwie layout_something definiują parametry układu elementu View, które są odpowiednie dla elementu ViewGroup, w którym się znajduje.

Każda klasa ViewGroup implementuje klasę zagnieżdżoną, która rozszerza klasę ViewGroup.LayoutParams. Ta podklasa zawiera typy właściwości, które określają rozmiar i położenie każdego widoku podrzędnego, odpowiednio do grupy widoków. Jak pokazano na rysunku 2, grupa widoku nadrzędnego definiuje parametry układu każdego widoku podrzędnego, w tym grupy widoku podrzędnego.

Rysunek 2. Wizualizacja hierarchii widoków z parametrami układu powiązanymi z każdym widokiem.

Każda podklasa LayoutParams ma własną składnię ustawiania wartości. Każdy element podrzędny musi definiować typ LayoutParams odpowiedni dla elementu nadrzędnego, ale może też definiować inny typ LayoutParams dla własnych elementów podrzędnych.

Wszystkie grupy widoków mają szerokość i wysokość określone za pomocą layout_widthlayout_height. Każdy widok musi je definiować. Wiele z nichLayoutParams ma opcjonalne marginesy i obramowania.

Możesz określić szerokość i wysokość za pomocą dokładnych pomiarów, ale nie warto tego robić zbyt często. Częściej używasz jednej z tych stałych do ustawiania szerokości lub wysokości:

  • wrap_content: informuje widok, aby dostosował swój rozmiar do wymiarów wymaganych przez jego zawartość.
  • match_parent: informuje widok, że ma być tak duży, jak pozwala na to jego nadrzędna grupa widoków.

Zasadniczo nie zalecamy określania szerokości i wysokości układu za pomocą jednostek bezwzględnych, takich jak piksele. Lepszym rozwiązaniem jest używanie pomiarów względnych, takich jak piksele niezależne od gęstości (dp), wrap_content lub match_parent, ponieważ pomaga to w prawidłowym wyświetlaniu aplikacji na ekranach urządzeń o różnych rozmiarach. Akceptowane typy pomiarów są zdefiniowane w zasobie układu.

Pozycja układu

Widok ma geometrię prostokątną. Ma ona położenie wyrażone jako para współrzędnych lewejgórnej oraz dwa wymiary wyrażone jako szerokość i wysokość. Jednostką lokalizacji i wymiarów jest piksel.

Lokalizację widoku możesz pobrać, wywołując metody getLeft() i getTop(). Pierwsza z nich zwraca współrzędną lewego górnego rogu (x) prostokąta reprezentującego widok. Ta druga funkcja zwraca górną współrzędną (y) prostokąta reprezentującego widok. Te metody zwracają lokalizację widoku względem jego elementu nadrzędnego. Jeśli na przykład getLeft() zwraca wartość 20, oznacza to, że widok znajduje się 20 pikseli na prawo od lewej krawędzi bezpośredniego elementu nadrzędnego.

Oprócz tego istnieją wygodne metody unikania niepotrzebnych obliczeń, a mianowicie getRight()getBottom(). Te metody zwracają współrzędne prawej i dolnej krawędzi prostokąta reprezentującego widok. Na przykład wywołanie getRight() jest podobne do tego obliczenia: getLeft() + getWidth().

Rozmiar, dopełnienie i marginesy

Rozmiar widoku jest określany za pomocą szerokości i wysokości. Widok ma 2 pary wartości szerokości i wysokości.

Pierwsza para to zmierzona szerokośćzmierzona wysokość. Te wymiary określają, jak duży ma być widok w ramach elementu nadrzędnego. Wymiary można uzyskać, wywołując funkcje getMeasuredWidth() i getMeasuredHeight().

Druga para to szerokośćwysokość, a czasami szerokość rysowaniawysokość rysowania. Te wymiary określają rzeczywisty rozmiar widoku na ekranie w momencie 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 uzyskać, wywołując funkcje getWidth()getHeight().

Aby zmierzyć wymiary widoku, uwzględnia on jego dopełnienie. Wartość dopełnienia jest wyrażona w pikselach dla lewej, górnej, prawej i dolnej części widoku. Możesz użyć dopełnienia, aby przesunąć zawartość widoku o określoną liczbę pikseli. Na przykład dopełnienie z lewej strony o wartości 2 przesuwa zawartość widoku o 2 piksele w prawo od lewej krawędzi. Dopełnienie możesz ustawić za pomocą metody setPadding(int, int, int, int) i sprawdzić, wywołując metody getPaddingLeft(), getPaddingTop(), getPaddingRight(), i  getPaddingBottom().

Widok może definiować dopełnienie, ale nie obsługuje marginesów. Grupy widoków obsługują jednak marginesy. Więcej informacji znajdziesz w sekcjach ViewGroupViewGroup.MarginLayoutParams.

Więcej informacji o wymiarach znajdziesz w artykule Wymiar.

Oprócz ustawiania marginesów i wypełnienia programowo możesz też ustawiać je w układach XML, jak pokazano 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>
  

W powyższym przykładzie widać zastosowanie marginesu i wypełnienia. Element TextView ma jednolite marginesy i dopełnienie zastosowane ze wszystkich stron, a element Button pokazuje, jak można je stosować niezależnie do różnych krawędzi.

Typowe układy

Każda podklasa klasy ViewGroup zapewnia unikalny sposób wyświetlania widoków zagnieżdżonych w niej. Najbardziej elastycznym typem układu, który zapewnia najlepsze narzędzia do utrzymania płytkiej hierarchii układu, jest ConstraintLayout.

Oto niektóre typowe rodzaje układów wbudowane w platformę Android:

Tworzenie układu liniowego

Umieszcza elementy podrzędne w jednym wierszu poziomym lub pionowym 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 wcześniej określona, możesz użyć elementu RecyclerView lub podklasy elementu AdapterView. RecyclerView jest zwykle lepszym rozwiązaniem, ponieważ efektywniej wykorzystuje pamięć niż AdapterView.

Typowe układy możliwe dzięki zastosowaniu elementów RecyclerViewAdapterView to:

Lista

Wyświetla przewijaną listę w jednej kolumnie.

Siatka

Wyświetla przewijaną siatkę kolumn i wierszy.

RecyclerView daje więcej możliwości i pozwala utworzyć niestandardowego menedżera układu.

Wypełnianie widoku adaptera danymi

Możesz wypełnić AdapterView, np. ListView lub GridView, przez powiązanie instancji AdapterView z Adapter, które pobiera dane ze źródła zewnętrznego i tworzy View reprezentujące każdy wpis danych.

Android udostępnia kilka podklas Adapter, które są przydatne do pobierania różnych rodzajów danych i tworzenia widoków dla AdapterView. Najczęściej używane są te 2 rodzaje adapterów:

ArrayAdapter
Użyj tego adaptera, gdy źródło danych jest tablicą. Domyślnie funkcja ArrayAdapter tworzy widok dla każdego elementu tablicy, wywołując funkcję toString() na każdym elemencie i umieszczając zawartość w elemencie TextView.

Jeśli na przykład masz tablicę ciągów znaków, które chcesz wyświetlić w ListView, zainicjuj nowy ArrayAdapter za pomocą konstruktora, aby określić układ każdego ciągu znaków i tablicy ciągów znakó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 to:

  • Twoja aplikacjaContext
  • Układ, który zawiera TextView dla każdego ciągu znaków w tablicy.
  • Tablica ciągów znaków

Następnie zadzwoń pod numer setAdapter() na urządzeniu ListView:

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() dla obiektów w tablicy. Możesz też utworzyć widok dla każdego elementu, który nie jest TextView – na przykład jeśli chcesz, aby każdy element tablicy był ImageView, rozszerz klasę ArrayAdapter i zastąp getView(), aby zwracać typ widoku, który chcesz uzyskać dla każdego elementu.

SimpleCursorAdapter
Użyj tego adaptera, gdy dane pochodzą z Cursor. Gdy używasz SimpleCursorAdapter, określ układ, który ma być używany w przypadku każdego wiersza w Cursor, oraz kolumny w Cursor, które chcesz wstawić do widoków 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 Cursor zawierającą wiersz dla każdej osoby oraz kolumny z imionami i nazwiskami oraz numerami. Następnie utwórz tablicę ciągów znaków określającą, które kolumny z Cursor mają być uwzględnione w układzie każdego wyniku, oraz tablicę liczb całkowitych określającą odpowiednie widoki, w których ma się znajdować każda kolumna:

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

Podczas tworzenia instancji klasy SimpleCursorAdapter przekaż układ, który ma być używany w przypadku każdego wyniku, obiekt Cursor 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 w Cursor, używając podanego układu przez wstawienie każdego elementu fromColumns do odpowiedniego widoku toViews.

Jeśli w trakcie działania aplikacji zmienisz dane podstawowe, które są odczytywane przez adapter, wywołaj funkcję notifyDataSetChanged(). Powiadamia to dołączony widok o zmianie danych i odświeża go.

Obsługa zdarzeń kliknięcia

Możesz reagować na zdarzenia kliknięcia każdego elementu w AdapterView za pomocą interfejsu 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 układy są używane w aplikacji demonstracyjnej Sunflower w GitHubie.