يطلب إطار عمل Android من
Activity
رسم التنسيق عندما يتم التركيز على Activity
. يعالج إطار عمل Android إجراء الرسم، ولكن
يجب أن يوفّر Activity
العقدة الجذرية للتسلسل الهرمي للتنسيق.
يرسم إطار عمل Android العقدة الأساسية للتنسيق ويقيسه ويرسم شجرة التنسيق. يرسم
من خلال المشي على الشجرة وعرض كل
View
يتداخل مع المنطقة غير الصالحة.
وتكون كل ViewGroup
مسؤولة عن طلب رسم كل عنصر من عناصرها الثانوية باستخدام طريقة draw()
، ويكون كل View
مسؤولاً عن رسم نفسه. وبما أنّه يتم تجاوز الشجرة حسب الطلب المسبق، يجذب إطار العمل الأهل قبل أولادهم (أي الخلف)، ويرسم الأشقاء بالترتيب الذي يظهرون به في الشجرة.
يرسم إطار عمل Android التخطيط من خلال عملية من مرّتين: تمريرة قياس وتمرير تخطيط. ينفِّذ إطار العمل مقياس الاجتياز في measure(int, int)
ويجري مسحًا من أعلى لأسفل لشجرة View
. يدفع كل View
مواصفات الأبعاد إلى أسفل الشجرة أثناء التكرار. في نهاية المقياس، يتم تخزين
كل قياساته في كل View
. يؤدي إطار العمل التمرير الثاني في
layout(int, int, int, int)
وهو أيضًا من أعلى إلى أسفل. خلال هذه التصريحات، يكون كل أب مسئولاً عن تحديد موقع جميع عناصره الثانوية باستخدام المقاسات المحسوبة في مسار القياس.
يتم وصف مرحلتي عملية التخطيط بمزيد من التفصيل في الأقسام التالية.
بدء إقرار القياس
عند عرض طريقة
measure()
لكائن View
، اضبط قيمتَي
getMeasuredWidth()
و
getMeasuredHeight()
مع قيمهما لكل العناصر التابعة لكائن View
. إنّ قيم العرض الذي تم قياسه وقيم الارتفاع التي تم قياسها لكائن View
يجب أن تتوافق مع القيود التي يفرضها العنصر الرئيسي
للكائن View
. يساعد هذا في التأكد من قبول جميع الأهل
لجميع قياسات أطفالهم في نهاية المقياس.
قد يتصل أحد الوالدَين View
بـ measure()
أكثر من مرة على عناصره الثانوية. على سبيل المثال، يمكن أن يقيس الوالد الأطفال مرة واحدة بأبعاد غير محدّدة لتحديد المقاسات المفضّلة لديهم. إذا كان مجموع المقاسات غير المحدودة للأطفال كبيرًا جدًا أو صغيرًا جدًا، قد يسمّى الوالد measure()
مرة أخرى باستخدام القيم التي تحدّ من مقاسات الأطفال.
يستخدم تصريح القياس فئتين لتوصيل الأبعاد. الفئة
ViewGroup.LayoutParams
هي طريقة توصيل عناصر View
بأحجامها ومواضعها المفضّلة. تصف فئة
ViewGroup.LayoutParams
الأساسية العرض والارتفاع المفضّلَين في
View
. بالنسبة إلى كل سمة، يمكن أن تحدد واحدًا مما يلي:
- بُعد دقيق.
MATCH_PARENT
، أي أنّ الحجم المفضّل لـView
هو حجم العنصر الرئيسي بدون المساحة المتروكة.WRAP_CONTENT
، أي أنّ المقاس المفضّل لـView
كبير بما يكفي لاحتواء المحتوى، بالإضافة إلى المساحة المتروكة.
تتوفّر فئات فرعية من ViewGroup.LayoutParams
للفئات الفرعية المختلفة من ViewGroup
. على سبيل المثال، يحتوي RelativeLayout
على فئته الفرعية ViewGroup.LayoutParams
الخاصة به، وهي تتيح إمكانية توسيط عناصر View
الثانوية أفقيًا وعموديًا.
تُستخدم كائنات MeasureSpec
لدفع المتطلبات إلى أسفل الشجرة من الأصل إلى التابع. يمكن وضع MeasureSpec
بطريقة من ثلاثة أوضاع:
UNSPECIFIED
: يستخدم العنصر الرئيسي هذه السمة لتحديد السمة المستهدفة لعنصرView
الثانوي. على سبيل المثال، قد يسمّىLinearLayout
بالرمزmeasure()
في عنصره الفرعي مع ضبط الارتفاع علىUNSPECIFIED
وعرضEXACTLY
240 لمعرفة طول الطفلView
بناءً على العرض الذي يبلغ 240 بكسل.EXACTLY
: يستخدم أحد الوالدَين هذه السمة لفرض المقاس بدقة على الطفل. ويجب أن يستخدم الطفل هذا المقاس مع ضمان أن تكون جميع عناصره الفرعية مناسبة لهذا المقاس.AT MOST
: يستخدم أحد الوالدَين هذه السمة لفرض حد أقصى لحجم الطفل. ويجب أن يضمن الطفل أن يكون هذا المقاس هو وجميع كل توابعه التابعة له.
بدء تمرير التخطيط
لبدء تصميم، يمكنك استدعاء
requestLayout()
. تُسمّى هذه الطريقة عادةً باستخدام السمة View
نفسها إذا رأت أنّها لم تعُد تستوفي حدودها.
تنفيذ منطق تصميم وقياس مخصّص
إذا كنت تريد تنفيذ منطق تنسيق أو قياس مخصّص، يمكنك إلغاء الطُرق التي يتم فيها تنفيذ المنطق:
onMeasure(int, int)
و
onLayout(boolean, int, int, int, int)
.
يتم استدعاء هاتين الطريقتين بواسطة measure(int, int)
وlayout(int, int, int, int)
، على التوالي. لا تحاول إلغاء الطريقتَين
measure(int, int)
أو layout(int, int)
، لأنّ كلتا الطريقتين
final
، لذلك لا يمكن إلغاءهما.
يوضِّح المثال التالي كيفية إجراء ذلك في فئة
`SplitLayout`
من نموذج تطبيق
WindowManager. إذا كان SplitLayout
يشتمل على عرضَين ثانويَين أو أكثر، وكانت الشاشة مزوّدة بطي، يتم وضع العرضَين الفرعيَين على جانبَي الجزء المرئي من الصفحة. يوضح المثال التالي حالة استخدام لتجاوز القياس والتنسيق، ولكن بالنسبة للإنتاج، استخدم
SlidingPaneLayout
إذا كنت تريد هذا السلوك.
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; } } }