Aplikacje mobilne · Układy

Układy I: VerticalStackLayout, HorizontalStackLayout i Grid

Układ (layout) decyduje, jak kontrolki rozmieszczają się na ekranie. Bez niego przyciski i pola nakładałyby się na siebie. MAUI musi dobrze działać na bardzo różnych ekranach, od małego telefonu po tablet, dlatego układy są tu szczególnie ważne. Zaczynamy od dwóch najczęstszych: układów stosowych i Grid.

  • około 45 minut
  • .NET MAUI · VerticalStackLayout · Grid
  • INF.04.6.2

Po tej lekcji: ułożyć formularz w VerticalStackLayout i HorizontalStackLayout, zbudować go też w Grid z wierszami i kolumnami (Auto, *) oraz użyć ColumnSpan.

Zobacz, zanim przeczytasz

Zobacz cztery ekrany, zanim zaczniesz

Każdy ekran to prawdziwy podgląd z kodu XAML. Poniżej rozbijemy je na cztery etapy. W każdym najpierw krótka wiedza, potem wyzwanie, a na końcu szybkie sprawdzenie.

Ta interakcja wymaga włączonego JavaScriptu.

Stos układa elementy w linii. Grid daje wiersze i kolumny. Zanim napiszesz kod, powiedz sobie: czy to jedna linia, czy tabela?

Etap 1 z 4

VerticalStackLayout: jedno pod drugim

Stos pionowy układa elementy w kolumnie. Używasz go do formularzy, kart i prostych ekranów. Dwie właściwości decydują o wyglądzie: Spacing (odstęp między elementami) i Padding (odstęp od krawędzi).

Wyzwanie 1 · stos pionowy

Zbuduj kartę powitalną

Odtwórz ekran. Cele odhaczają się na żywo.

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Dwa szybkie pytania

Ta interakcja wymaga włączonego JavaScriptu.

Etap 2 z 4

HorizontalStackLayout: obok siebie

Stos poziomy ustawia elementy w jednym rzędzie, od lewej do prawej. Pasuje do pary przycisków, rzędu ikon albo pola z etykietą. Uwaga: gdy zabraknie miejsca, elementy nie zawijają się, tylko wychodzą poza ekran.

Wyzwanie 2 · czytanie kodu

Który ekran zobaczysz?

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie 3 · stos poziomy

Dwa przyciski obok siebie

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Szybkie pytanie

Ta interakcja wymaga włączonego JavaScriptu.

Etap 3 z 4

Grid: wiersze i kolumny

Grid dzieli ekran na komórki. Każdy element dostaje adres: Grid.Row i Grid.Column (numeracja od 0). Rozmiar toru ustawiasz tak: Auto dopasowuje do zawartości, liczba daje stałe piksele, * bierze resztę miejsca, a 2* bierze dwa razy więcej niż *.

4 · Siatka

Jak opisać siatkę

WłaściwośćOpis
Grid.RowDefinitions / Grid.ColumnDefinitionsLiczba i rozmiar wierszy i kolumn.
Height="Auto" / Width="Auto"Wiersz lub kolumna zajmuje tyle, ile potrzebuje zawartość.
Height="*" / Width="*"Elastycznie wypełnia resztę miejsca. Możliwe też 2*, 3* dla proporcji.
Grid.Row / Grid.ColumnUstawiane na dziecku: w której komórce ma być element.
Grid.ColumnSpan / Grid.RowSpanRozciąga element na kilka kolumn lub wierszy.
RowSpacing / ColumnSpacingOdstępy między wierszami i kolumnami.
Wskazówka egzaminacyjna: gdy zadanie wymaga precyzyjnego ułożenia etykiet i pól w kolumnach, zacznij od `Grid`.

Wyzwanie 4 · czytanie kodu

Dlaczego widać tylko jedno pole?

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie 5 · siatka

Siatka 2×2

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie 6 · cudzy kod

Dlaczego ten formularz jest zepsuty?

Ta interakcja wymaga włączonego JavaScriptu.

Sprawdź, czy zapamiętałeś

Trzy szybkie pytania

Ta interakcja wymaga włączonego JavaScriptu.

Etap 4 z 4

ColumnSpan: element na kilka kolumn

Nagłówek albo duży przycisk często mają zająć całą szerokość formularza. Służy do tego Grid.ColumnSpan (w poziomie) i Grid.RowSpan (w pionie).

Wyzwanie 7 · rozciąganie

Nagłówek i przycisk na całą szerokość

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie 8 · kod za widokiem

Ułóż plik `.xaml.cs`

Ta interakcja wymaga włączonego JavaScriptu.

Wyzwanie końcowe

Odtwórz ekran logowania

Połącz wszystko: Grid, kolumny, ColumnSpan.

Ta interakcja wymaga włączonego JavaScriptu.

Powtórka końcowa

Cztery pytania z całej lekcji

Ta interakcja wymaga włączonego JavaScriptu.

7 · Sprawdź się

Który układ?

Cztery sytuacje z ekranami.

Ta interakcja wymaga włączonego JavaScriptu.

Dla chętnych · po pierwszej stronie

Pracownia w Visual Studio: zbuduj ten formularz w prawdziwym projekcie

Rozwiń dodatkowe warsztaty ↓

Wyzwania powyżej uczą układów w przeglądarce. Tu masz pełną instrukcję krok po kroku, jak zrobić to samo w Visual Studio i uruchomić na emulatorze, tak jak na egzaminie praktycznym. Otwórz, gdy będziesz gotowy do pracy z projektem.

1. Przygotuj miejsce pracy

Co dodajesz, a co podmieniasz?

Stronę dodaj przez Projekt → Dodaj → Nowy element → .NET MAUI ContentPage (XAML). Powstaje para .xaml i .xaml.cs. Dla ContentView wybierz szablon ContentView; dla modeli i serwisów zwykłą klasę. Jeśli plik już istnieje, podmień wskazaną zawartość — nie twórz drugiej klasy o tej samej nazwie.

PlikRola w ćwiczeniu
RejestracjaStosPage.xamlWygląd strony lub kontrolki
RejestracjaGridPage.xamlWygląd strony lub kontrolki
Grid — fragment XAMLFragment w istniejącym pliku
Fragment.xamlFragment w istniejącym pliku

Zanim uruchomisz: trzy połączenia

x:Class="MojaAplikacja.NazwaStrony" w XAML musi zgadzać się z namespace MojaAplikacja i nazwą klasy C#. InitializeComponent() tworzy kontrolki opisane w XAML.

x:Name="PoleImie" daje kodowi C# dostęp przez PoleImie.Text. Z kolei Clicked="Zapisz_Clicked" wskazuje metodę o tej nazwie w pliku .xaml.cs. Sam napis przycisku w Text niczego nie podpina.

Gdy dodajesz XAML z obsługą zdarzeń, od razu uzupełnij też odpowiadający C#. Uruchamiaj dopiero po skończeniu oznaczonej pary plików; brakujący handler nie jest błędem środowiska.

2. Uruchom sam wygląd — jeszcze bez logiki

W świeżym projekcie zastąp MainPage.xaml poniższym ekranem. W MainPage.xaml.cs zachowaj konstruktor z InitializeComponent(); usuń szablonowy licznik i OnCounterClicked, bo nie ma ich już w XAML. Na tym etapie App i AppShell pozostają z szablonu. To pełny, tymczasowy XAML — później świadomie go podmienisz, nie dokleisz do drugiego ContentPage.

MainPage.xaml — etap tymczasowyxaml
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.MainPage">
    <VerticalStackLayout Padding="24" Spacing="14">
        <Label Text="Rejestracja" FontSize="24" />
        <Entry Placeholder="Login" />
        <Entry Placeholder="Hasło" IsPassword="True" />
    </VerticalStackLayout>
</ContentPage>
  1. Pierwsza próba Uruchom. Zobaczysz kontrolki, ale przyciski bez Clicked jeszcze nic nie robią. Sprawdź rozmiar i kolejność elementów. Zatrzymaj aplikację przed następnym etapem.

3. Wskaż stronę startową ćwiczenia

Po dodaniu docelowych stron podmień App.xaml.cs na poniższy. App.xaml zostaje z szablonu. Od tej chwili startuje RejestracjaStosPage, a nie wcześniejszy tymczasowy MainPage z AppShell. Uzupełnij jej pliki w kolejnych etapach, zanim ponownie uruchomisz projekt.

App.xaml.cscsharp
namespace MojaAplikacja;

public partial class App : Application
{
    public App() => InitializeComponent();
    protected override Window CreateWindow(IActivationState? activationState)
        => new Window(new NavigationPage(new RejestracjaStosPage()));
}
  1. CreateWindow Tworzy okno aplikacji. NavigationPage jest kontenerem zwykłych stron i daje przycisk powrotu; nie łączymy go z Shell.
  2. new RejestracjaStosPage() Tworzy instancję docelowej strony. Aby testować drugi niezależny przykład, zatrzymaj program, zmień tylko ten typ i uruchom ponownie.

4. Połącz wygląd z działaniem

Teraz zbuduj docelowy przykład. Pełne pliki poniżej zastępują swoje wcześniejsze wersje. Przeczytaj opis nad kodem i objaśnienia pod nim; potem sprawdź przepływ danych, nie tylko brak czerwonych podkreśleń.

Gdy przykład ma kilka plików zależnych od siebie, najpierw dodaj wszystkie wskazane typy, a potem uruchom całość. Ustaw breakpoint w obsłudze głównego przycisku i prześledź jedną próbę.

Budowa krok po kroku

RejestracjaStosPage.xaml

Dodaj ContentPage (XAML) RejestracjaStosPage i podmień jej XAML. Zostaw wygenerowany konstruktor w pliku .xaml.cs. Ten ekran pokazuje rozmieszczenie; przyciski nie mają jeszcze handlerów.

Najszybszy sposób na prosty formularz: nagłówek, dwa pola i poziomy rząd przycisków na dole.

RejestracjaStosPage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.RejestracjaStosPage">
    <VerticalStackLayout Padding="20" Spacing="15">

        <Label Text="Rejestracja"
               FontSize="24"
               FontAttributes="Bold"
               HorizontalOptions="Center" />

        <Entry x:Name="PoleEmail" Placeholder="E-mail" />
        <Entry x:Name="PoleHaslo" Placeholder="Hasło" IsPassword="True" />

        <!-- Poziomy rząd przycisków na dole formularza -->
        <HorizontalStackLayout Spacing="10" HorizontalOptions="Center">
            <Button Text="Anuluj" BackgroundColor="Gray" TextColor="White" WidthRequest="100" />
            <Button Text="Zarejestruj" BackgroundColor="RoyalBlue" TextColor="White" WidthRequest="100" />
        </HorizontalStackLayout>

    </VerticalStackLayout>
</ContentPage>
  1. Padding="20" Spacing="15" Margines wewnętrzny kontenera i odstęp między elementami.
  2. IsPassword="True" Ukrywa wpisywane znaki w Entry.
  3. HorizontalStackLayout w środku Zagnieżdżony poziomy układ z dwoma przyciskami obok siebie.
  4. HorizontalOptions="Center" Wyśrodkowanie elementu w poziomie.
  5. WidthRequest="100" Żądana szerokość przycisku.

Budowa krok po kroku

RejestracjaGridPage.xaml

Dodaj osobną RejestracjaGridPage. Nie wklejaj jej jako drugiego korzenia do poprzedniego pliku. Wiersze i kolumny ustalają wspólne pozycje etykiet i pól.

Trzy wiersze (dwa Auto i jeden *) oraz dwie kolumny (Auto na etykiety, * na pola).

RejestracjaGridPage.xamlxaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="MojaAplikacja.RejestracjaGridPage">
    <Grid Padding="20" RowSpacing="12" ColumnSpacing="10">

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Label Text="E-mail:" Grid.Row="0" Grid.Column="0" VerticalTextAlignment="Center" />
        <Entry Placeholder="podaj e-mail" Grid.Row="0" Grid.Column="1" />

        <Label Text="Hasło:" Grid.Row="1" Grid.Column="0" VerticalTextAlignment="Center" />
        <Entry IsPassword="True" Placeholder="********" Grid.Row="1" Grid.Column="1" />

        <!-- ColumnSpan="2" rozciąga przycisk na całą szerokość formularza -->
        <Button Text="Zarejestruj"
                Grid.Row="2" Grid.ColumnSpan="2"
                BackgroundColor="RoyalBlue" TextColor="White" />

    </Grid>
</ContentPage>
  1. <RowDefinition Height="Auto" /> Wiersz dopasowany do pola. Ostatni wiersz * bierze resztę miejsca.
  2. <ColumnDefinition Width="Auto" /> i "*" Kolumna etykiet jest wąska, a kolumna pól zajmuje resztę szerokości.
  3. Grid.Row="0" Grid.Column="1" Adres komórki: wiersz i kolumna liczone od zera.
  4. Grid.ColumnSpan="2" Przycisk obejmuje obie kolumny, więc jest wyśrodkowany względem całego formularza.
  5. VerticalTextAlignment="Center" Etykieta wyrównana w pionie do pola obok.

Budowa krok po kroku

Ten sam Grid zapisany krócej

To alternatywny fragment zawartości strony, nie nowy plik projektu. Podmień nim dotychczasowy kontener wewnątrz ContentPage; nie wklejaj całego Grid do wnętrza innego Grid bez potrzeby.

RowDefinitions="Auto,*,Auto" odpowiada trzem RowDefinition z Height Auto, * i Auto. ColumnDefinitions="*,2*" dzieli pozostałą szerokość w proporcji 1:2. Stała liczba oznacza jednostki niezależne od gęstości, nie zawsze fizyczne piksele.

Grid — fragment XAMLcsharp
<Grid RowDefinitions="Auto,*,Auto" ColumnDefinitions="*,2*"
      RowSpacing="12" ColumnSpacing="12">
    <Label Text="Nagłówek" Grid.ColumnSpan="2" />
    <Label Text="Menu" Grid.Row="1" />
    <Label Text="Treść" Grid.Row="1" Grid.Column="1" />
    <Button Text="Zapisz" Grid.Row="2" Grid.ColumnSpan="2" />
</Grid>
  1. Grid.Row="1" Indeksy zaczynają się od zera; to drugi wiersz.
  2. Auto / * / 2* Rozmiar zawartości / udział dostępnego miejsca / podwójny udział. Gwiazdka potrzebuje skończonego miejsca od rodzica.

Budowa krok po kroku

Klasyczny StackLayout: odczytaj Orientation

To porównanie z klasycznym StackLayout. Orientation określa kierunek. Dwa przyciski są zawartością jednej kontrolki układu.

StackLayout spotkasz w starszych projektach i poleceniach. Orientation="Horizontal" układa wiersz, Vertical kolumnę. W nowych prostych układach używaj wyspecjalizowanych HorizontalStackLayout/VerticalStackLayout.

Fragment.xamlxaml
<StackLayout Orientation="Horizontal" Spacing="12">
    <Button Text="Tak" />
    <Button Text="Nie" />
</StackLayout>
  1. Przeczytaj powiązanie x:Name identyfikuje kontrolkę, a Binding odczytuje właściwość obiektu ustawionego jako BindingContext. To dwa różne mechanizmy.

Plik .xaml.cs też musi istnieć

Szablon strony lub ContentView generuje poniższe klasy. Nie dodawaj ich drugi raz, jeśli Visual Studio już je utworzyło. W każdej parze musi pozostać InitializeComponent().

RejestracjaStosPage — minimalny kod za widokiem

RejestracjaStosPage.xaml.cscsharp
namespace MojaAplikacja;

public partial class RejestracjaStosPage : ContentPage
{
    public RejestracjaStosPage() => InitializeComponent();
}
  1. partial Druga część klasy powstaje z XAML. Nie twórz ręcznie pól kontrolek ani drugiej metody InitializeComponent.

RejestracjaGridPage — minimalny kod za widokiem

RejestracjaGridPage.xaml.cscsharp
namespace MojaAplikacja;

public partial class RejestracjaGridPage : ContentPage
{
    public RejestracjaGridPage() => InitializeComponent();
}
  1. partial Druga część klasy powstaje z XAML. Nie twórz ręcznie pól kontrolek ani drugiej metody InitializeComponent.

5. Odbiór: czy naprawdę działa?

Wykonaj próby w swojej aplikacji, nie tylko w modelu na stronie. Zaznaczaj dopiero po sprawdzeniu wyniku. Funkcje urządzenia wymagają testu na właściwej platformie.

6 · Czego unikać

Cztery błędy z układami

Stos do bardzo długiej listy
Tak nie
<VerticalStackLayout>
    <!-- 500 etykiet w pętli -->
</VerticalStackLayout>

Aplikacja zwalnia, a przewijanie szarpie.

Tak
<CollectionView ItemsSource="{Binding Pozycje}" />

CollectionView tworzy tylko widoczne elementy.

Grid bez definicji wierszy i kolumn
Tak nie
<Grid>
    <Label Text="A" Grid.Row="1" />
    <Label Text="B" Grid.Row="2" />
</Grid>

Grid ma jeden wiersz i jedną kolumnę, więc wszystko nakłada się w jednej komórce.

Tak
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition /> <RowDefinition /> <RowDefinition />
    </Grid.RowDefinitions>
    ...
</Grid>

Numery Grid.Row odnoszą się do zdefiniowanych wierszy.

Pomylenie `Auto` z `*`
Tak nie
<ColumnDefinition Width="Auto" />   <!-- na długi tekst -->

Kolumna pęcznieje i spycha resztę poza ekran.

Tak
<ColumnDefinition Width="*" />

Auto dopasowuje się do treści, * zajmuje resztę dostępnego miejsca.

Test tylko na jednym ekranie
Tak nie
Sprawdzony wyłącznie na dużym telefonie

Na małym ekranie układ się „rozjeżdża”.

Tak
Uruchom na kilku wirtualnych urządzeniach o różnych rozmiarach

Aplikacja mobilna musi działać na różnych ekranach.

Ćwiczenie z pomocą · samodzielna praca

6. Mała zmiana, którą zrobisz samodzielnie

Dodaj do siatki wiersz z polem Miasto, zachowując wyrównanie kolumn.

Oczekiwany rezultat
Dodaj do siatki wiersz z polem Miasto, zachowując wyrównanie kolumn.
Dotychczasowe działanie ma pozostać poprawne.
Potrzebujesz pomocy? Rozwiązuj krok po kroku.
Krok 1 — pokaż podpowiedź

Dodaj RowDefinition przed wierszem przycisku.

Krok 2 — pokaż podpowiedź

Etykieta i Entry dostają ten sam nowy Grid.Row i różne Grid.Column.

Krok 3 — pokaż podpowiedź

Przesuń przycisk o jeden wiersz; indeksy zaczynają się od zera.

Otwórz tylko tyle podpowiedzi, ile potrzebujesz. Po każdym kroku wróć do zadania i sprawdź wynik.

Podsumowanie

Co warto zapamiętać

  1. VerticalStackLayout układa w pionie, HorizontalStackLayout w poziomie. Mają Padding i Spacing.
  2. Układy stosowe łączysz, ale nie używasz ich do długich list. Tam jest CollectionView.
  3. Grid: definiujesz RowDefinitions i ColumnDefinitions, a dziecku wskazujesz Grid.Row i Grid.Column.
  4. Auto dopasowuje do treści, * wypełnia resztę, 2* to proporcje. ColumnSpan i RowSpan rozciągają element.
  5. Układ sprawdzaj na kilku rozmiarach ekranu.

Dalej na swoim koncie

Rozgrzewki, quiz i zadania są w biurku ucznia.

Teoria jest otwarta dla wszystkich. Żeby rozwiązywać zadania, zapisywać szkice i widzieć swój postęp, zaloguj się.

Zaloguj się →
Źródła i dalsza nauka