Udostępnij za pośrednictwem


Tworzenie niestandardowego układu w programie Xamarin.Forms

Xamarin.Forms Definiuje pięć klas układu — StackLayout, AbsoluteLayout, RelativeLayout, Grid i FlexLayout, a każda z nich rozmieszcza swoje elementy podrzędne w inny sposób. Czasami jednak konieczne jest organizowanie zawartości strony przy użyciu układu, który nie jest dostarczany przez Xamarin.Formsprogram . W tym artykule wyjaśniono, jak napisać niestandardową klasę układu i przedstawiono klasę WrapLayout z uwzględnieniem orientacji, która rozmieszcza swoje elementy podrzędne w poziomie na stronie, a następnie opakowuje wyświetlanie kolejnych elementów podrzędnych do dodatkowych wierszy.

W Xamarin.Formssystemie wszystkie klasy układu pochodzą z Layout<T> klasy i ogranicz typ ogólny do View i jego typów pochodnych. Z kolei Layout<T> klasa pochodzi z Layout klasy, która zapewnia mechanizm pozycjonowania i określania rozmiaru elementów podrzędnych.

Każdy element wizualizacji jest odpowiedzialny za określenie własnego preferowanego rozmiaru, który jest nazywany żądanym rozmiarem. Page, Layouti Layout<View> typy pochodne są odpowiedzialne za określenie lokalizacji i rozmiaru ich dziecka lub dzieci względem siebie. W związku z tym układ obejmuje relację nadrzędny-podrzędny, w której element nadrzędny określa, jaki powinien być rozmiar jego elementów podrzędnych, ale podejmie próbę dostosowania żądanego rozmiaru elementu podrzędnego.

Do utworzenia układu niestandardowego wymagane jest dokładne zrozumienie Xamarin.Forms układu i cykli unieważniania. Te cykle zostaną teraz omówione.

Układ

Układ zaczyna się w górnej części drzewa wizualizacji ze stroną i przechodzi przez wszystkie gałęzie drzewa wizualnego, aby objąć każdy element wizualizacji na stronie. Elementy, które są rodzicami innych elementów, są odpowiedzialne za ustalanie rozmiaru i pozycjonowanie swoich dzieci względem siebie.

Klasa VisualElement definiuje metodę Measure , która mierzy element dla operacji układu, a Layout metoda określająca prostokątny obszar, w ramach którego element zostanie renderowany. Po uruchomieniu aplikacji i wyświetleniu pierwszej strony cykl układu składający się najpierw z Measure wywołań, a następnie Layout wywołania uruchamia się na Page obiekcie:

  1. Podczas cyklu układu każdy element nadrzędny jest odpowiedzialny za wywoływanie Measure metody dla jej elementów podrzędnych.
  2. Po zmierzeniu elementów podrzędnych każdy element nadrzędny jest odpowiedzialny za wywoływanie Layout metody dla jej elementów podrzędnych.

Ten cykl gwarantuje, że każdy element wizualizacji na stronie odbiera wywołania Measure metod i Layout . Proces pokazano na poniższym diagramie:

Xamarin.Forms Cykl układu

Uwaga

Należy pamiętać, że cykle układu mogą również występować w podzestawie drzewa wizualnego, jeśli coś się zmieni, aby wpłynąć na układ. Obejmuje to dodawanie lub usuwanie elementów z kolekcji, takich jak w StackLayoutobiekcie , zmiana IsVisible właściwości elementu lub zmiana rozmiaru elementu.

Każda Xamarin.Forms klasa, która ma Content właściwość lub Children , ma metodę przesłoniętą LayoutChildren . Niestandardowe klasy układu pochodzące z Layout<View> klasy muszą zastąpić tę metodę i upewnić się, że Measure metody i Layout są wywoływane dla wszystkich elementów podrzędnych elementu, aby zapewnić żądany układ niestandardowy.

Ponadto każda klasa, która pochodzi z Layout metody lub Layout<View> musi przesłonić OnMeasure metodę, w której klasa układu określa rozmiar, który musi być przez wywołania Measure metod jej elementów podrzędnych.

Uwaga

Elementy określają ich rozmiar na podstawie ograniczeń, które wskazują, ile miejsca jest dostępne dla elementu nadrzędnego elementu. Ograniczenia przekazywane do Measure metod i OnMeasure mogą wahać się od 0 do Double.PositiveInfinity. Element jest ograniczony lub w pełni ograniczony, gdy odbiera wywołanie Measure metody z argumentami nieskończenie nieskończonymi — element jest ograniczony do określonego rozmiaru. Element nie jest ograniczony lub częściowo ograniczony, gdy odbiera wywołanie Measure metody z co najmniej jednym argumentem równym Double.PositiveInfinity — nieskończone ograniczenie można traktować jako wskazujące autoskalowanie.

Unieważnienie

Unieważnienie to proces, przez który zmiana elementu na stronie wyzwala nowy cykl układu. Elementy są uznawane za nieprawidłowe, gdy nie mają już poprawnego rozmiaru ani położenia. Na przykład, jeśli FontSize właściwość Button zmian, mówi się, że jest nieprawidłowa, Button ponieważ nie będzie już mieć poprawnego rozmiaru. Zmiana rozmiaru Button może mieć wpływ na zmiany układu w pozostałej części strony.

Elementy unieważniają się przez wywołanie InvalidateMeasure metody, zazwyczaj gdy właściwość elementu ulegnie zmianie, co może spowodować nowy rozmiar elementu. Ta metoda uruchamia MeasureInvalidated zdarzenie, które element nadrzędny obsługuje w celu wyzwolenia nowego cyklu układu.

Klasa Layout ustawia procedurę obsługi dla zdarzenia dla MeasureInvalidated każdego elementu podrzędnego dodanego do jego Content właściwości lub Children kolekcji i odłącza program obsługi po usunięciu elementu podrzędnego. W związku z tym każdy element w drzewie wizualnym, który zawiera elementy podrzędne, jest powiadamiany za każdym razem, gdy jeden z jego elementów podrzędnych zmienia rozmiar. Na poniższym diagramie pokazano, jak zmiana rozmiaru elementu w drzewie wizualnym może spowodować zmiany, które falują drzewo:

Unieważnienie w drzewie wizualnym

Layout Jednak klasa próbuje ograniczyć wpływ zmiany rozmiaru elementu podrzędnego na układ strony. Jeśli układ ma ograniczenie rozmiaru, zmiana rozmiaru podrzędnego nie ma wpływu na coś wyższego niż układ nadrzędny w drzewie wizualizacji. Jednak zazwyczaj zmiana rozmiaru układu wpływa na sposób rozmieszczania elementów podrzędnych w układzie. W związku z tym każda zmiana rozmiaru układu rozpocznie cykl układu dla układu, a układ będzie otrzymywać wywołania do jego OnMeasure metod i LayoutChildren .

Klasa Layout definiuje również metodę InvalidateLayout podobną do InvalidateMeasure metody . Metoda InvalidateLayout powinna być wywoływana za każdym razem, gdy zostanie wprowadzona zmiana, która wpływa na położenie układu i rozmiar jego elementów podrzędnych. Na przykład klasa wywołuje metodę InvalidateLayout za każdym razem, Layout gdy element podrzędny zostanie dodany do lub usunięty z układu.

Element InvalidateLayout może zostać zastąpiony w celu zaimplementowania pamięci podręcznej w celu zminimalizowania powtarzających się wywołań Measure metod elementów podrzędnych układu. InvalidateLayout Zastąpienie metody spowoduje powiadomienie o dodaniu lub usunięciu elementów podrzędnych z układu. Podobnie można zastąpić metodę, OnChildMeasureInvalidated aby podać powiadomienie, gdy jeden z elementów podrzędnych układu zmienia rozmiar. W przypadku obu przesłonięć metod układ niestandardowy powinien odpowiadać przez wyczyszczenie pamięci podręcznej. Aby uzyskać więcej informacji, zobacz Calculate and Cache Layout Data (Obliczanie i buforowanie danych układu).

Tworzenie układu niestandardowego

Proces tworzenia układu niestandardowego jest następujący:

  1. Utwórz klasę pochodną od klasy Layout<View>. Aby uzyskać więcej informacji, zobacz Create a WrapLayout (Tworzenie elementu WrapLayout).

  2. [opcjonalnie] Dodaj właściwości oparte na właściwościach możliwych do powiązania dla wszystkich parametrów, które powinny być ustawione w klasie układu. Aby uzyskać więcej informacji, zobacz Dodawanie właściwości wspieranych przez właściwości możliwe do powiązania.

  3. Zastąpi metodę OnMeasure , aby wywołać metodę Measure dla wszystkich elementów podrzędnych układu i zwrócić żądany rozmiar układu. Aby uzyskać więcej informacji, zobacz Zastępowanie metody OnMeasure.

  4. Zastąpi metodę LayoutChildren , aby wywołać metodę Layout we wszystkich elementach podrzędnych układu. Niepowodzenie wywołania Layout metody dla każdego elementu podrzędnego w układzie spowoduje, że element podrzędny nigdy nie otrzymuje poprawnego rozmiaru lub położenia, dlatego element podrzędny nie będzie widoczny na stronie. Aby uzyskać więcej informacji, zobacz Zastępowanie metody LayoutChildren.

    Uwaga

    Podczas wyliczania elementów podrzędnych w przesłonięciach OnMeasure i LayoutChildren pomiń wszystkie elementy podrzędne, których IsVisible właściwość jest ustawiona na false. Dzięki temu układ niestandardowy nie pozostawi miejsca dla niewidocznych elementów podrzędnych.

  5. [opcjonalnie] Zastąpij metodę InvalidateLayout , aby otrzymywać powiadomienia, gdy elementy podrzędne zostaną dodane do układu lub usunięte z niego. Aby uzyskać więcej informacji, zobacz Zastępowanie metody InvalidateLayout.

  6. [opcjonalnie] Zastąpij metodę OnChildMeasureInvalidated , aby otrzymywać powiadomienia, gdy jeden z elementów podrzędnych układu zmienia rozmiar. Aby uzyskać więcej informacji, zobacz Zastępowanie metody OnChildMeasureInvalidated.

Uwaga

Należy pamiętać, że OnMeasure przesłonięcia nie będą wywoływane, jeśli rozmiar układu jest zarządzany przez jego element nadrzędny, a nie jego elementy podrzędne. Jednak przesłonięcia będą wywoływane, jeśli jedno lub oba ograniczenia są nieskończone lub jeśli klasa układu ma wartości inne niż domyślne HorizontalOptions lub VerticalOptions właściwości. Z tego powodu LayoutChildren przesłonięcia nie mogą polegać na rozmiarach podrzędnych uzyskanych podczas wywołania OnMeasure metody. LayoutChildren Zamiast tego należy wywołać metodę Measure dla elementów podrzędnych układu przed wywołaniem Layout metody . Alternatywnie rozmiar elementów podrzędnych uzyskanych w OnMeasure przesłonięciu może być buforowany, aby uniknąć późniejszego Measure wywołania w LayoutChildren przesłonięciu, ale klasa układu będzie musiała wiedzieć, kiedy należy ponownie uzyskać rozmiary. Aby uzyskać więcej informacji, zobacz Calculate and Cache Layout Data (Obliczanie i buforowanie danych układu).

Następnie można użyć klasy układu, dodając ją do Pageelementu , i dodając elementy podrzędne do układu. Aby uzyskać więcej informacji, zobacz Korzystanie z elementu WrapLayout.

Tworzenie obiektu WrapLayout

Przykładowa aplikacja demonstruje klasę wrażliwą na WrapLayout orientację, która rozmieszcza swoje elementy podrzędne w poziomie na stronie, a następnie opakowuje wyświetlanie kolejnych elementów podrzędnych do dodatkowych wierszy.

Klasa WrapLayout przydziela taką samą ilość miejsca dla każdego elementu podrzędnego, znanego jako rozmiar komórki, na podstawie maksymalnego rozmiaru elementów podrzędnych. Elementy podrzędne mniejsze niż rozmiar komórki można umieścić w komórce na podstawie ich HorizontalOptions wartości właściwości i VerticalOptions .

Definicja klasy jest wyświetlana WrapLayout w poniższym przykładzie kodu:

public class WrapLayout : Layout<View>
{
  Dictionary<Size, LayoutData> layoutDataCache = new Dictionary<Size, LayoutData>();
  ...
}

Obliczanie i buforowanie danych układu

Struktura LayoutData przechowuje dane dotyczące kolekcji elementów podrzędnych w wielu właściwościach:

  • VisibleChildCount — liczba elementów podrzędnych widocznych w układzie.
  • CellSize – maksymalny rozmiar wszystkich elementów podrzędnych, dostosowany do rozmiaru układu.
  • Rows — liczba wierszy.
  • Columns — liczba kolumn.

Pole layoutDataCache jest używane do przechowywania wielu LayoutData wartości. Po uruchomieniu aplikacji dwa LayoutData obiekty będą buforowane w słowniku layoutDataCache dla bieżącej orientacji — jeden dla argumentów ograniczeń do przesłonięcia, a jeden dla width argumentów OnMeasure i height do LayoutChildren przesłonięcia. Podczas rotacji urządzenia w orientację OnMeasure poziomą przesłonięcia i LayoutChildren przesłonięcia zostaną ponownie wywołane, co spowoduje buforowanie kolejnych dwóch LayoutData obiektów w słowniku. Jednak podczas zwracania urządzenia do orientacji pionowej nie są wymagane żadne dalsze obliczenia, ponieważ layoutDataCache te dane są już wymagane.

Poniższy przykład kodu przedstawia metodę GetLayoutData , która oblicza właściwości LayoutData struktury na podstawie określonego rozmiaru:

LayoutData GetLayoutData(double width, double height)
{
  Size size = new Size(width, height);

  // Check if cached information is available.
  if (layoutDataCache.ContainsKey(size))
  {
    return layoutDataCache[size];
  }

  int visibleChildCount = 0;
  Size maxChildSize = new Size();
  int rows = 0;
  int columns = 0;
  LayoutData layoutData = new LayoutData();

  // Enumerate through all the children.
  foreach (View child in Children)
  {
    // Skip invisible children.
    if (!child.IsVisible)
      continue;

    // Count the visible children.
    visibleChildCount++;

    // Get the child's requested size.
    SizeRequest childSizeRequest = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity);

    // Accumulate the maximum child size.
    maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
    maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
  }

  if (visibleChildCount != 0)
  {
    // Calculate the number of rows and columns.
    if (Double.IsPositiveInfinity(width))
    {
      columns = visibleChildCount;
      rows = 1;
    }
    else
    {
      columns = (int)((width + ColumnSpacing) / (maxChildSize.Width + ColumnSpacing));
      columns = Math.Max(1, columns);
      rows = (visibleChildCount + columns - 1) / columns;
    }

    // Now maximize the cell size based on the layout size.
    Size cellSize = new Size();

    if (Double.IsPositiveInfinity(width))
      cellSize.Width = maxChildSize.Width;
    else
      cellSize.Width = (width - ColumnSpacing * (columns - 1)) / columns;

    if (Double.IsPositiveInfinity(height))
      cellSize.Height = maxChildSize.Height;
    else
      cellSize.Height = (height - RowSpacing * (rows - 1)) / rows;

    layoutData = new LayoutData(visibleChildCount, cellSize, rows, columns);
  }

  layoutDataCache.Add(size, layoutData);
  return layoutData;
}

Metoda GetLayoutData wykonuje następujące operacje:

  • Określa, czy wartość obliczeniowa LayoutData znajduje się już w pamięci podręcznej i zwraca ją, jeśli jest dostępna.
  • W przeciwnym razie wylicza wszystkie elementy podrzędne, wywołując Measure metodę dla każdego elementu podrzędnego o nieskończonej szerokości i wysokości, i określa maksymalny rozmiar elementu podrzędnego.
  • Pod warunkiem, że istnieje co najmniej jeden widoczny element podrzędny, oblicza liczbę wymaganych wierszy i kolumn, a następnie oblicza rozmiar komórki dla elementów podrzędnych na podstawie wymiarów WrapLayoutelementu . Należy pamiętać, że rozmiar komórki jest zwykle nieco szerszy niż maksymalny rozmiar dziecka, ale może być również mniejszy, jeśli WrapLayout nie jest wystarczająco szeroki dla najszerszego dziecka lub wystarczająco wysoki dla najwyższego dziecka.
  • Przechowuje nową LayoutData wartość w pamięci podręcznej.

Dodawanie właściwości wspieranych przez właściwości możliwe do powiązania

Klasa WrapLayout definiuje ColumnSpacing i RowSpacing właściwości, których wartości są używane do oddzielania wierszy i kolumn w układzie oraz które są wspierane przez powiązane właściwości. Właściwości możliwe do powiązania są wyświetlane w poniższym przykładzie kodu:

public static readonly BindableProperty ColumnSpacingProperty = BindableProperty.Create(
  "ColumnSpacing",
  typeof(double),
  typeof(WrapLayout),
  5.0,
  propertyChanged: (bindable, oldvalue, newvalue) =>
  {
    ((WrapLayout)bindable).InvalidateLayout();
  });

public static readonly BindableProperty RowSpacingProperty = BindableProperty.Create(
  "RowSpacing",
  typeof(double),
  typeof(WrapLayout),
  5.0,
  propertyChanged: (bindable, oldvalue, newvalue) =>
  {
    ((WrapLayout)bindable).InvalidateLayout();
  });

Program obsługi zmienionych właściwości każdej właściwości możliwej do powiązania wywołuje InvalidateLayout zastąpienie metody w celu wyzwolenia nowego układu przekazywanego w WrapLayoutobiekcie . Aby uzyskać więcej informacji, zobacz Zastąp metodę InvalidateLayout i zastąp metodę OnChildMeasureInvalidated.

Zastąpij metodę OnMeasure

Przesłonięcia OnMeasure pokazano w poniższym przykładzie kodu:

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
  LayoutData layoutData = GetLayoutData(widthConstraint, heightConstraint);
  if (layoutData.VisibleChildCount == 0)
  {
    return new SizeRequest();
  }

  Size totalSize = new Size(layoutData.CellSize.Width * layoutData.Columns + ColumnSpacing * (layoutData.Columns - 1),
                layoutData.CellSize.Height * layoutData.Rows + RowSpacing * (layoutData.Rows - 1));
  return new SizeRequest(totalSize);
}

Przesłonięcia wywołuje metodę GetLayoutData i konstruuje SizeRequest obiekt z zwracanych danych, a także uwzględnia RowSpacing wartości właściwości i ColumnSpacing . Aby uzyskać więcej informacji na temat GetLayoutData metody, zobacz Calculate and Cache Layout Data (Obliczanie i buforowanie danych układu).

Ważne

Measure Metody i OnMeasure nigdy nie powinny żądać nieskończonego wymiaru, zwracając SizeRequest wartość z właściwością ustawioną na Double.PositiveInfinity. Jednak co najmniej jeden z argumentów OnMeasure ograniczeń może mieć wartość Double.PositiveInfinity.

Zastąpij metodę LayoutChildren

Przesłonięcia LayoutChildren pokazano w poniższym przykładzie kodu:

protected override void LayoutChildren(double x, double y, double width, double height)
{
  LayoutData layoutData = GetLayoutData(width, height);

  if (layoutData.VisibleChildCount == 0)
  {
    return;
  }

  double xChild = x;
  double yChild = y;
  int row = 0;
  int column = 0;

  foreach (View child in Children)
  {
    if (!child.IsVisible)
    {
      continue;
    }

    LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), layoutData.CellSize));
    if (++column == layoutData.Columns)
    {
      column = 0;
      row++;
      xChild = x;
      yChild += RowSpacing + layoutData.CellSize.Height;
    }
    else
    {
      xChild += ColumnSpacing + layoutData.CellSize.Width;
    }
  }
}

Przesłonięcia zaczynają się od wywołania GetLayoutData metody, a następnie wylicza wszystkie elementy podrzędne do rozmiaru i umieszczania ich w komórce każdego elementu podrzędnego. Jest to osiągane przez wywołanie LayoutChildIntoBoundingRegion metody , która służy do umieszczania elementu podrzędnego w prostokątze na podstawie jej HorizontalOptions wartości właściwości i VerticalOptions . Jest to równoważne wywołaniu metody podrzędnej Layout .

Uwaga

Należy pamiętać, że prostokąt przekazany do LayoutChildIntoBoundingRegion metody zawiera cały obszar, w którym może znajdować się element podrzędny.

Aby uzyskać więcej informacji na temat GetLayoutData metody, zobacz Calculate and Cache Layout Data (Obliczanie i buforowanie danych układu).

Zastąpij metodę InvalidateLayout

Przesłonięcia InvalidateLayout są wywoływane, gdy elementy podrzędne są dodawane do układu lub usuwane z układu albo gdy jedna z WrapLayout właściwości zmienia wartość, jak pokazano w poniższym przykładzie kodu:

protected override void InvalidateLayout()
{
  base.InvalidateLayout();
  layoutInfoCache.Clear();
}

Zastąpić unieważnia układ i odrzuca wszystkie buforowane informacje o układzie.

Uwaga

Aby zatrzymać Layout wywołanie InvalidateLayout klasy za każdym razem, gdy element podrzędny zostanie dodany do lub usunięty z układu, przesłonięć ShouldInvalidateOnChildAdded metody i ShouldInvalidateOnChildRemoved i zwrócić wartość false. Klasa układu może następnie zaimplementować proces niestandardowy po dodaniu lub usunięciu elementów podrzędnych.

Zastąp metodę OnChildMeasureInvalidated

Przesłonięcia OnChildMeasureInvalidated jest wywoływane, gdy jeden z elementów podrzędnych układu zmienia rozmiar i jest wyświetlany w poniższym przykładzie kodu:

protected override void OnChildMeasureInvalidated()
{
  base.OnChildMeasureInvalidated();
  layoutInfoCache.Clear();
}

Zastąpić unieważnia układ podrzędny i odrzuca wszystkie buforowane informacje o układzie.

Korzystanie z obiektu WrapLayout

Klasę WrapLayout można użyć, umieszczając ją na typie pochodnym Page , jak pokazano w poniższym przykładzie kodu XAML:

<ContentPage ... xmlns:local="clr-namespace:ImageWrapLayout">
    <ScrollView Margin="0,20,0,20">
        <local:WrapLayout x:Name="wrapLayout" />
    </ScrollView>
</ContentPage>

Równoważny kod języka C# jest pokazany poniżej:

public class ImageWrapLayoutPageCS : ContentPage
{
  WrapLayout wrapLayout;

  public ImageWrapLayoutPageCS()
  {
    wrapLayout = new WrapLayout();

    Content = new ScrollView
    {
      Margin = new Thickness(0, 20, 0, 20),
      Content = wrapLayout
    };
  }
  ...
}

Następnie można dodać elementy podrzędne do elementu zgodnie z WrapLayout wymaganiami. Poniższy przykład kodu przedstawia Image elementy dodawane do elementu WrapLayout:

protected override async void OnAppearing()
{
    base.OnAppearing();

    var images = await GetImageListAsync();
    if (images != null)
    {
        foreach (var photo in images.Photos)
        {
            var image = new Image
            {
                Source = ImageSource.FromUri(new Uri(photo))
            };
            wrapLayout.Children.Add(image);
        }
    }
}

async Task<ImageList> GetImageListAsync()
{
    try
    {
        string requestUri = "https://raw.githubusercontent.com/xamarin/docs-archive/master/Images/stock/small/stock.json";
        string result = await _client.GetStringAsync(requestUri);
        return JsonConvert.DeserializeObject<ImageList>(result);
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"\tERROR: {ex.Message}");
    }

    return null;
}

Gdy zostanie wyświetlona strona zawierająca WrapLayout element, przykładowa aplikacja asynchronicznie uzyskuje dostęp do zdalnego pliku JSON zawierającego listę zdjęć, tworzy Image element dla każdego zdjęcia i dodaje go do pliku WrapLayout. Spowoduje to wyświetlenie wyglądu pokazanego na poniższych zrzutach ekranu:

Przykładowe zrzuty ekranu pionowe aplikacji

Na poniższych zrzutach ekranu przedstawiono WrapLayout po obróceniu do orientacji poziomej:

Przykładowy zrzut ekranu poziomego aplikacji systemu iOSPrzykładowy zrzut ekranu poziomego aplikacji systemu AndroidPrzykładowy zrzut ekranu poziomego aplikacji platformy UWP

Liczba kolumn w każdym wierszu zależy od rozmiaru zdjęcia, szerokości ekranu i liczby pikseli na jednostkę niezależną od urządzenia. Image Elementy asynchronicznie ładują zdjęcia, a zatem WrapLayout klasa będzie otrzymywać częste wywołania metodyLayoutChildren, ponieważ każdy Image element otrzymuje nowy rozmiar na podstawie załadowanego zdjęcia.