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 elementuView
to rozmiar elementu nadrzędnego, minus dopełnienie.WRAP_CONTENT
, co oznacza, że preferowany rozmiar elementuView
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ędnegoView
. Przykład:LinearLayout
może zadzwonićmeasure()
w elemencie podrzędnym z wysokością ustawioną naUNSPECIFIED
i szerokością zEXACTLY
240 aby dowiedzieć się, jaką wysokość chce mieć dzieckoView
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 ListsplitPositions = 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; } } }