Jak Android generuje wyświetlenia

Platforma Androida wymaga Activity, aby narysować układ, gdy Activity skupia się. Procedurę rysowania służy platforma Androida, ale Activity musi zawierać węzeł główny swojej hierarchii układu.

Platforma Androida pobiera węzeł główny układu, dokonuje pomiarów i rysuje drzewo układu. it rysuje, spacerując po drzewie, View, który przecina nieprawidłowy region. Każdy podmiot typu ViewGroup jest odpowiedzialny za żądania narysowania każdego elementu podrzędnego za pomocą draw() i każdy element View odpowiada za narysowanie się siebie. Ponieważ udało się przejść przez drzewo zamówienia w przedsprzedaży, system przyciąga rodziców wcześniej, czyli za nimi. dzieci i rysuje rodzeństwo w kolejności, w jakiej występują na drzewie.

Platforma Androida rysuje układ w ramach procesu dwuetapowego: zaliczenia testu i zaliczenia układu. umożliwia przekazywanie danych measure(int, int) i wykonuje przemierzanie od góry drzewa View. Każda wartość View przekazuje wymiar podczas rekurencji. Po zaliczeniu pomiaru każdy View przechowuje wyniki pomiarów. Platforma wykonuje drugi etap przebiegu layout(int, int, int, int) i od góry. W tym okresie każdy rodzic jest odpowiedzialny za umieszczenie wszystkich swoich elementów podrzędnych za pomocą rozmiarów obliczonych w ramach zaliczenia testu.

Dwa etapy procesu tworzenia układu opisujemy bardziej szczegółowo w kolejnych sekcjach.

Rozpocznij procedurę zaliczania pomiarów

Gdy obiekt View Metoda measure() zwraca, ustaw jego getMeasuredWidth() oraz getMeasuredHeight() wraz z tymi dla wszystkich elementów podrzędnych obiektu View. View mierzona szerokość i wysokość obiektu muszą uwzględniać ograniczenia nałożone przez Elementy nadrzędne obiektu View. Dzięki temu na koniec zaliczenia testu wszyscy rodzice akceptują wszystkie pomiary danych dziecka.

Element nadrzędny View może wywołać metodę measure() więcej niż raz w swoich elementach podrzędnych. Dla: Na przykład element nadrzędny może raz mierzyć elementy podrzędne z nieokreślonymi wymiarami, aby określić ich preferowanych rozmiarów. Jeśli suma nieograniczonych rozmiarów elementów podrzędnych jest za duża lub za mała, element nadrzędny może ponownie wywołać funkcję measure() z wartościami, które ograniczają rozmiary elementów podrzędnych.

Miara komunikuje się za pomocą 2 klas. ViewGroup.LayoutParams to sposób, w jaki obiekty View przekazują preferowane rozmiary i pozycje. Podstawa Klasa ViewGroup.LayoutParams opisuje preferowaną szerokość i wysokość View W przypadku każdego wymiaru można w nim określić jedną z tych wartości:

  • Dokładny wymiar.
  • MATCH_PARENT, co oznacza, że preferowany rozmiar elementu View to rozmiar elementu nadrzędnego, minus dopełnienie.
  • WRAP_CONTENT, co oznacza, że preferowany rozmiar elementu View jest po prostu wystarczająco duży, aby pomieścić zawartość, plus dopełnienie.

Istnieją podklasy klasy ViewGroup.LayoutParams dla różnych podklas ViewGroup Przykład: RelativeLayout ma własne podklasa klasy ViewGroup.LayoutParams, która obejmuje możliwość wyśrodkowania elementu podrzędnego View obiektów w poziomie i pionie.

MeasureSpec obiektów jest służy do przenoszenia wymagań z elementu nadrzędnego na element podrzędny. Element MeasureSpec może znajdować się w: jeden z trzech trybów:

  • UNSPECIFIED: element nadrzędny używa go do określania wymiaru docelowego elementu podrzędnego View. Przykład: LinearLayout może zadzwonić measure() w elemencie podrzędnym z wysokością ustawioną na UNSPECIFIED i szerokością z EXACTLY 240 aby dowiedzieć się, jaką wysokość chce mieć dziecko View przy szerokości 240 pikseli.
  • EXACTLY: element nadrzędny używa go do nałożenia dokładnego rozmiaru na element podrzędny. Dziecko musi używać tego rozmiaru, zagwarantuje, że wszystkie jego elementy podrzędne mieszczą się w tym rozmiarze.
  • AT MOST: element nadrzędny używa go do nałożenia maksymalnego rozmiaru na element podrzędny. Dziecko musi zagwarantować, że wszystkie jego elementy podrzędne mieszczą się w tym rozmiarze.

Inicjowanie karty układu

Aby zainicjować układ, wywołaj requestLayout() Ten jest zwykle wywoływana przez View, gdy uzna, że nie pasuje w swoich granicach.

Implementowanie niestandardowych pomiarów i logiki układu

Jeśli chcesz wdrożyć niestandardowy pomiar lub logikę układu, zastąp metody, w których jest zaimplementowany: onMeasure(int, int) oraz onLayout(boolean, int, int, int, int) Te metody są wywoływane przez funkcję measure(int, int) i layout(int, int, int, int). Nie próbuj zastępować parametru measure(int, int) lub layout(int, int) – obie te metody. mają wartość final, więc nie można ich zastąpić.

Z przykładu poniżej dowiesz się, jak to zrobić w `SplitLayout` z zajęć Menedżer okien przykładową aplikację. Jeśli SplitLayout ma co najmniej dwa widoki podrzędne, a ekran jest składany, umieszcza dwa widoki podrzędne po obu stronach części strony widocznej na ekranie. Poniższy przykład pokazuje zastosowanie do zastąpienia pomiarów i układu, ale w środowisku produkcyjnym użyj funkcji SlidingPaneLayout aby zapewnić takie zachowanie.

Kotlin

/**
 * An example of split-layout for two views, separated by a display
 * feature that goes across the window. When both start and end views are
 * added, it checks whether there are display features that separate the area
 * in two—such as a fold or hinge—and places them side-by-side or
 * top-bottom.
 */
class SplitLayout : FrameLayout {
   private var windowLayoutInfo: WindowLayoutInfo? = null
   private var startViewId = 0
   private var endViewId = 0

   private var lastWidthMeasureSpec: Int = 0
   private var lastHeightMeasureSpec: Int = 0

   ...

   fun updateWindowLayout(windowLayoutInfo: WindowLayoutInfo) {
      this.windowLayoutInfo = windowLayoutInfo
      requestLayout()
   }

   override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
      val startView = findStartView()
      val endView = findEndView()
      val splitPositions = splitViewPositions(startView, endView)

      if (startView != null && endView != null && splitPositions != null) {
            val startPosition = splitPositions[0]
            val startWidthSpec = MeasureSpec.makeMeasureSpec(startPosition.width(), EXACTLY)
            val startHeightSpec = MeasureSpec.makeMeasureSpec(startPosition.height(), EXACTLY)
            startView.measure(startWidthSpec, startHeightSpec)
            startView.layout(
               startPosition.left, startPosition.top, startPosition.right,
               startPosition.bottom
            )

            val endPosition = splitPositions[1]
            val endWidthSpec = MeasureSpec.makeMeasureSpec(endPosition.width(), EXACTLY)
            val endHeightSpec = MeasureSpec.makeMeasureSpec(endPosition.height(), EXACTLY)
            endView.measure(endWidthSpec, endHeightSpec)
            endView.layout(
               endPosition.left, endPosition.top, endPosition.right,
               endPosition.bottom
            )
      } else {
            super.onLayout(changed, left, top, right, bottom)
      }
   }

   /**
   * Gets the position of the split for this view.
   * @return A rect that defines of split, or {@code null} if there is no split.
   */
   private fun splitViewPositions(startView: View?, endView: View?): Array? {
      if (windowLayoutInfo == null || startView == null || endView == null) {
            return null
      }

      // Calculate the area for view's content with padding.
      val paddedWidth = width - paddingLeft - paddingRight
      val paddedHeight = height - paddingTop - paddingBottom

      windowLayoutInfo?.displayFeatures
            ?.firstOrNull { feature -> isValidFoldFeature(feature) }
            ?.let { feature ->
               getFeaturePositionInViewRect(feature, this)?.let {
                  if (feature.bounds.left == 0) { // Horizontal layout.
                        val topRect = Rect(
                           paddingLeft, paddingTop,
                           paddingLeft + paddedWidth, it.top
                        )
                        val bottomRect = Rect(
                           paddingLeft, it.bottom,
                           paddingLeft + paddedWidth, paddingTop + paddedHeight
                        )

                        if (measureAndCheckMinSize(topRect, startView) &&
                           measureAndCheckMinSize(bottomRect, endView)
                        ) {
                           return arrayOf(topRect, bottomRect)
                        }
                  } else if (feature.bounds.top == 0) { // Vertical layout.
                        val leftRect = Rect(
                           paddingLeft, paddingTop,
                           it.left, paddingTop + paddedHeight
                        )
                        val rightRect = Rect(
                           it.right, paddingTop,
                           paddingLeft + paddedWidth, paddingTop + paddedHeight
                        )

                        if (measureAndCheckMinSize(leftRect, startView) &&
                           measureAndCheckMinSize(rightRect, endView)
                        ) {
                           return arrayOf(leftRect, rightRect)
                        }
                  }
               }
            }

      // You previously tried to fit the children and measure them. Since they
      // don't fit, measure again to update the stored values.
      measure(lastWidthMeasureSpec, lastHeightMeasureSpec)
      return null
   }

   override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec)
      lastWidthMeasureSpec = widthMeasureSpec
      lastHeightMeasureSpec = heightMeasureSpec
   }

   /**
   * Measures a child view and sees if it fits in the provided rect.
   * This method calls [View.measure] on the child view, which updates its
   * stored values for measured width and height. If the view ends up with
   * different values, measure again.
   */
   private fun measureAndCheckMinSize(rect: Rect, childView: View): Boolean {
      val widthSpec = MeasureSpec.makeMeasureSpec(rect.width(), AT_MOST)
      val heightSpec = MeasureSpec.makeMeasureSpec(rect.height(), AT_MOST)
      childView.measure(widthSpec, heightSpec)
      return childView.measuredWidthAndState and MEASURED_STATE_TOO_SMALL == 0 &&
               childView.measuredHeightAndState and MEASURED_STATE_TOO_SMALL == 0
   }

   private fun isValidFoldFeature(displayFeature: DisplayFeature) =
      (displayFeature as? FoldingFeature)?.let { feature ->
            getFeaturePositionInViewRect(feature, this) != null
      } ?: false
}

Java

/**
* An example of split-layout for two views, separated by a display feature
* that goes across the window. When both start and end views are added, it checks
* whether there are display features that separate the area in two—such as
* fold or hinge—and places them side-by-side or top-bottom.
*/
public class SplitLayout extends FrameLayout {
   @Nullable
   private WindowLayoutInfo windowLayoutInfo = null;
   private int startViewId = 0;
   private int endViewId = 0;

   private int lastWidthMeasureSpec = 0;
   private int lastHeightMeasureSpec = 0;

   ...

   void updateWindowLayout(WindowLayoutInfo windowLayoutInfo) {
      this.windowLayoutInfo = windowLayoutInfo;
      requestLayout();
   }

   @Override
   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      @Nullable
      View startView = findStartView();
      @Nullable
      View endView = findEndView();
      @Nullable
      List splitPositions = splitViewPositions(startView, endView);

      if (startView != null && endView != null && splitPositions != null) {
            Rect startPosition = splitPositions.get(0);
            int startWidthSpec = MeasureSpec.makeMeasureSpec(startPosition.width(), EXACTLY);
            int startHeightSpec = MeasureSpec.makeMeasureSpec(startPosition.height(), EXACTLY);
            startView.measure(startWidthSpec, startHeightSpec);
            startView.layout(
                  startPosition.left,
                  startPosition.top,
                  startPosition.right,
                  startPosition.bottom
            );

            Rect endPosition = splitPositions.get(1);
            int endWidthSpec = MeasureSpec.makeMeasureSpec(endPosition.width(), EXACTLY);
            int endHeightSpec = MeasureSpec.makeMeasureSpec(endPosition.height(), EXACTLY);
            startView.measure(endWidthSpec, endHeightSpec);
            startView.layout(
                  endPosition.left,
                  endPosition.top,
                  endPosition.right,
                  endPosition.bottom
            );
      } else {
            super.onLayout(changed, left, top, right, bottom);
      }
   }

   /**
   * Gets the position of the split for this view.
   * @return A rect that defines of split, or {@code null} if there is no split.
   */
   @Nullable
   private List splitViewPositions(@Nullable View startView, @Nullable View endView) {
      if (windowLayoutInfo == null || startView == null || endView == null) {
            return null;
      }

      int paddedWidth = getWidth() - getPaddingLeft() - getPaddingRight();
      int paddedHeight = getHeight() - getPaddingTop() - getPaddingBottom();

      List displayFeatures = windowLayoutInfo.getDisplayFeatures();

      @Nullable
      DisplayFeature feature = displayFeatures
               .stream()
               .filter(item ->
                  isValidFoldFeature(item)
               )
               .findFirst()
               .orElse(null);

      if (feature != null) {
            Rect position = SampleToolsKt.getFeaturePositionInViewRect(feature, this, true);
            Rect featureBounds = feature.getBounds();
            if (featureBounds.left == 0) { // Horizontal layout.
               Rect topRect = new Rect(
                        getPaddingLeft(),
                        getPaddingTop(),
                        getPaddingLeft() + paddedWidth,
                        position.top
               );
               Rect bottomRect = new Rect(
                        getPaddingLeft(),
                        position.bottom,
                        getPaddingLeft() + paddedWidth,
                        getPaddingTop() + paddedHeight
               );
               if (measureAndCheckMinSize(topRect, startView) &&
                        measureAndCheckMinSize(bottomRect, endView)) {
                  ArrayList rects = new ArrayList();
                  rects.add(topRect);
                  rects.add(bottomRect);
                  return rects;
               }
            } else if (featureBounds.top == 0) { // Vertical layout.
               Rect leftRect = new Rect(
                        getPaddingLeft(),
                        getPaddingTop(),
                        position.left,
                        getPaddingTop() + paddedHeight
               );
               Rect rightRect = new Rect(
                        position.right,
                        getPaddingTop(),
                        getPaddingLeft() + paddedWidth,
                        getPaddingTop() + paddedHeight
               );
               if (measureAndCheckMinSize(leftRect, startView) &&
                        measureAndCheckMinSize(rightRect, endView)) {
                  ArrayList rects = new ArrayList();
                  rects.add(leftRect);
                  rects.add(rightRect);
                  return rects;
               }
            }
      }

      // You previously tried to fit the children and measure them. Since
      // they don't fit, measure again to update the stored values.
      measure(lastWidthMeasureSpec, lastHeightMeasureSpec);
      return null;
   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      lastWidthMeasureSpec = widthMeasureSpec;
      lastHeightMeasureSpec = heightMeasureSpec;
   }

   /**
   * Measures a child view and sees if it fits in the provided rect.
   * This method calls [View.measure] on the child view, which updates
   * its stored values for measured width and height. If the view ends up with
   * different values, measure again.
   */
   private boolean measureAndCheckMinSize(Rect rect, View childView) {
      int widthSpec = MeasureSpec.makeMeasureSpec(rect.width(), AT_MOST);
      int heightSpec = MeasureSpec.makeMeasureSpec(rect.height(), AT_MOST);
      childView.measure(widthSpec, heightSpec);
      return (childView.getMeasuredWidthAndState() & MEASURED_STATE_TOO_SMALL) == 0 &&
               (childView.getMeasuredHeightAndState() & MEASURED_STATE_TOO_SMALL) == 0;
   }

   private boolean isValidFoldFeature(DisplayFeature displayFeature) {
      if (displayFeature instanceof FoldingFeature) {
            return SampleToolsKt.getFeaturePositionInViewRect(displayFeature, this, true) != null;
      } else {
            return false;
      }
   }
}