How Android draws views

Try the Compose way
Jetpack Compose is the recommended UI toolkit for Android. Learn about Compose phases.

The Android framework asks an Activity to draw its layout when the Activity receives focus. The Android framework handles the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.

The Android framework draws the root node of the layout and measures and draws the layout tree. It draws by walking the tree and rendering each View that intersects the invalid region. Each ViewGroup is responsible for requesting that each of its children be drawn, using the draw() method, and each View is responsible for drawing itself. Because the tree is traversed pre-order, the framework draws parents before—in other words, behind—their children, and it draws siblings in the order they appear in the tree.

The Android framework draws the layout in a two-pass process: a measure pass and a layout pass. The framework performs the measure pass in measure(int, int) and performs a top-down traversal of the View tree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every View stores its measurements. The framework performs the second pass in layout(int, int, int, int) and is also top-down. During this pass, each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

The two passes of the layout process are described in more detail in the following sections.

Initiate a measure pass

When a View object's measure() method returns, set its getMeasuredWidth() and getMeasuredHeight() values, along with those for all of the View object's descendants. A View object's measured width and measured height values must respect the constraints imposed by the View object's parents. This helps ensure that at the end of the measure pass, all parents accept all of their children's measurements.

A parent View might call measure() more than once on its children. For example, the parent might measure the children once with unspecified dimensions to determine their preferred sizes. If the sum of the children's unconstrained sizes is too big or too small, the parent might call measure() again with values that constrain the children's sizes.

The measure pass uses two classes to communicate dimensions. The ViewGroup.LayoutParams class is how View objects communicate their preferred sizes and positions. The base ViewGroup.LayoutParams class describes the preferred width and height of the View. For each dimension, it can specify one of the following:

  • An exact dimension.
  • MATCH_PARENT , meaning the preferred size for the View is the size of its parent, minus padding.
  • WRAP_CONTENT , meaning the preferred size for the View is just big enough to enclose its content, plus padding.

There are subclasses of ViewGroup.LayoutParams for different subclasses of ViewGroup. For example, RelativeLayout has its own subclass of ViewGroup.LayoutParams that includes the ability to center child View objects horizontally and vertically.

MeasureSpec objects are used to push requirements down the tree from parent to child. A MeasureSpec can be in one of three modes:

  • UNSPECIFIED : the parent uses this to determine the target dimension of a child View. For example, a LinearLayout might call measure() on its child with the height set to UNSPECIFIED and a width of EXACTLY 240 to find out how tall the child View wants to be, given a width of 240 pixels.
  • EXACTLY : the parent uses this to impose an exact size on the child. The child must use this size and guarantee that all of its descendants fit within this size.
  • AT MOST : the parent uses this to impose a maximum size on the child. The child must guarantee that it and all of its descendants fit within this size.

Initiate a layout pass

To initiate a layout, call requestLayout() . This method is typically called by a View on itself when it believes it can no longer fit within its bounds.

Implement a custom measurement and layout logic

If you want to implement a custom measurement or layout logic, override the methods where the logic is implemented: onMeasure(int, int) and onLayout(boolean, int, int, int, int) . These methods are called by measure(int, int) and layout(int, int, int, int), respectively. Don't try to override the measure(int, int) or layout(int, int) methods—both of these methods are final, so they can't be overridden.

The following example shows how to do this in the `SplitLayout` class from the WindowManager sample app. If the SplitLayout has two or more child views, and the display has a fold, then it positions the two child views on either side of the fold. The following example shows a use case for overriding the measurement and layout, but for production, use SlidingPaneLayout if you want this behavior.

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.
 */
classSplitLayout:FrameLayout{
privatevarwindowLayoutInfo:WindowLayoutInfo? =null
privatevarstartViewId=0
privatevarendViewId=0
privatevarlastWidthMeasureSpec:Int=0
privatevarlastHeightMeasureSpec:Int=0
...
funupdateWindowLayout(windowLayoutInfo:WindowLayoutInfo){
this.windowLayoutInfo=windowLayoutInfo
requestLayout()
}
overridefunonLayout(changed:Boolean,left:Int,top:Int,right:Int,bottom:Int){
valstartView=findStartView()
valendView=findEndView()
valsplitPositions=splitViewPositions(startView,endView)
if(startView!=null&&endView!=null&&splitPositions!=null){
valstartPosition=splitPositions[0]
valstartWidthSpec=MeasureSpec.makeMeasureSpec(startPosition.width(),EXACTLY)
valstartHeightSpec=MeasureSpec.makeMeasureSpec(startPosition.height(),EXACTLY)
startView.measure(startWidthSpec,startHeightSpec)
startView.layout(
startPosition.left,startPosition.top,startPosition.right,
startPosition.bottom
)
valendPosition=splitPositions[1]
valendWidthSpec=MeasureSpec.makeMeasureSpec(endPosition.width(),EXACTLY)
valendHeightSpec=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.
 */
privatefunsplitViewPositions(startView:View?,endView:View?):Array? {
if(windowLayoutInfo==null||startView==null||endView==null){
returnnull
}
// Calculate the area for view's content with padding.
valpaddedWidth=width-paddingLeft-paddingRight
valpaddedHeight=height-paddingTop-paddingBottom
windowLayoutInfo?.displayFeatures
?.firstOrNull{feature->isValidFoldFeature(feature)}
?.let{feature->
getFeaturePositionInViewRect(feature,this)?.let{
if(feature.bounds.left==0){// Horizontal layout.
valtopRect=Rect(
paddingLeft,paddingTop,
paddingLeft+paddedWidth,it.top
)
valbottomRect=Rect(
paddingLeft,it.bottom,
paddingLeft+paddedWidth,paddingTop+paddedHeight
)
if(measureAndCheckMinSize(topRect,startView)&&
measureAndCheckMinSize(bottomRect,endView)
){
returnarrayOf(topRect,bottomRect)
}
}elseif(feature.bounds.top==0){// Vertical layout.
valleftRect=Rect(
paddingLeft,paddingTop,
it.left,paddingTop+paddedHeight
)
valrightRect=Rect(
it.right,paddingTop,
paddingLeft+paddedWidth,paddingTop+paddedHeight
)
if(measureAndCheckMinSize(leftRect,startView)&&
measureAndCheckMinSize(rightRect,endView)
){
returnarrayOf(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)
returnnull
}
overridefunonMeasure(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.
 */
privatefunmeasureAndCheckMinSize(rect:Rect,childView:View):Boolean{
valwidthSpec=MeasureSpec.makeMeasureSpec(rect.width(),AT_MOST)
valheightSpec=MeasureSpec.makeMeasureSpec(rect.height(),AT_MOST)
childView.measure(widthSpec,heightSpec)
returnchildView.measuredWidthAndStateandMEASURED_STATE_TOO_SMALL==0&&
childView.measuredHeightAndStateandMEASURED_STATE_TOO_SMALL==0
}
privatefunisValidFoldFeature(displayFeature:DisplayFeature)=
(displayFeatureas?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.
*/
publicclass SplitLayoutextendsFrameLayout{
@Nullable
privateWindowLayoutInfowindowLayoutInfo=null;
privateintstartViewId=0;
privateintendViewId=0;
privateintlastWidthMeasureSpec=0;
privateintlastHeightMeasureSpec=0;
...
voidupdateWindowLayout(WindowLayoutInfowindowLayoutInfo){
this.windowLayoutInfo=windowLayoutInfo;
requestLayout();
}
@Override
protectedvoidonLayout(booleanchanged,intleft,inttop,intright,intbottom){
@Nullable
ViewstartView=findStartView();
@Nullable
ViewendView=findEndView();
@Nullable
ListsplitPositions=splitViewPositions(startView,endView);
if(startView!=null&&endView!=null&&splitPositions!=null){
RectstartPosition=splitPositions.get(0);
intstartWidthSpec=MeasureSpec.makeMeasureSpec(startPosition.width(),EXACTLY);
intstartHeightSpec=MeasureSpec.makeMeasureSpec(startPosition.height(),EXACTLY);
startView.measure(startWidthSpec,startHeightSpec);
startView.layout(
startPosition.left,
startPosition.top,
startPosition.right,
startPosition.bottom
);
RectendPosition=splitPositions.get(1);
intendWidthSpec=MeasureSpec.makeMeasureSpec(endPosition.width(),EXACTLY);
intendHeightSpec=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
privateListsplitViewPositions(@NullableViewstartView,@NullableViewendView){
if(windowLayoutInfo==null||startView==null||endView==null){
returnnull;
}
intpaddedWidth=getWidth()-getPaddingLeft()-getPaddingRight();
intpaddedHeight=getHeight()-getPaddingTop()-getPaddingBottom();
ListdisplayFeatures=windowLayoutInfo.getDisplayFeatures();
@Nullable
DisplayFeaturefeature=displayFeatures
.stream()
.filter(item->
isValidFoldFeature(item)
)
.findFirst()
.orElse(null);
if(feature!=null){
Rectposition=SampleToolsKt.getFeaturePositionInViewRect(feature,this,true);
RectfeatureBounds=feature.getBounds();
if(featureBounds.left==0){// Horizontal layout.
RecttopRect=newRect(
getPaddingLeft(),
getPaddingTop(),
getPaddingLeft()+paddedWidth,
position.top
);
RectbottomRect=newRect(
getPaddingLeft(),
position.bottom,
getPaddingLeft()+paddedWidth,
getPaddingTop()+paddedHeight
);
if(measureAndCheckMinSize(topRect,startView)&&
measureAndCheckMinSize(bottomRect,endView)){
ArrayListrects=newArrayList();
rects.add(topRect);
rects.add(bottomRect);
returnrects;
}
}elseif(featureBounds.top==0){// Vertical layout.
RectleftRect=newRect(
getPaddingLeft(),
getPaddingTop(),
position.left,
getPaddingTop()+paddedHeight
);
RectrightRect=newRect(
position.right,
getPaddingTop(),
getPaddingLeft()+paddedWidth,
getPaddingTop()+paddedHeight
);
if(measureAndCheckMinSize(leftRect,startView)&&
measureAndCheckMinSize(rightRect,endView)){
ArrayListrects=newArrayList();
rects.add(leftRect);
rects.add(rightRect);
returnrects;
}
}
}
// 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);
returnnull;
}
@Override
protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){
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.
 */
privatebooleanmeasureAndCheckMinSize(Rectrect,ViewchildView){
intwidthSpec=MeasureSpec.makeMeasureSpec(rect.width(),AT_MOST);
intheightSpec=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;
}
privatebooleanisValidFoldFeature(DisplayFeaturedisplayFeature){
if(displayFeatureinstanceofFoldingFeature){
returnSampleToolsKt.getFeaturePositionInViewRect(displayFeature,this,true)!=null;
}else{
returnfalse;
}
}
}

Content and code samples on this page are subject to the licenses described in the Content License. Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.

Last updated 2026年05月28日 UTC.