Measure… Layout… Draw!
This time we would focus on the Measure/Layout phase, which determines each view’s size and position, so that we can draw it.
Step 1: Measure
Goal: Determine the view size.
The size includes the view’s descendants size, and has to be agreed by the view parent.
View size is defined in 2 ways:
measured width & measured height — how big a view wants to be within its parent. This is the size we’re looking for on this step.
width & height (aka: drawing width & drawing height) — Actual size on screen, at drawing time and after layout. This will be figured out later on in step 2
How does it work?
Top-down recursive traversal of the view tree.
Each view hands dimension requirements to its descendants.
How? the parent defines for each child’s height and width one of the 3 MeasureSpec class options:
UNSPECIFIED: the child can be as big as it wants.
EXACTLY: the child should be on an exact size.
AT MOST: the child can be as big as it wants up to some maximum.
Each view’s height and width preferences are defined by one of the 3 ViewGroup.LayoutParams class options:
An exact number
MATCH_PARENT: the child wants to be as big as its parent
WRAP_CONTENT: the view wants to be as big as its content
The measurement is done on the method: onMeasure(int widthMeasureSpec, int heightMeasureSpec)
Notice that this process sometimes a negotiation between a view and its children, and so measure() may be called more than once. More on that on a later post.
Since the traversal is top down, and each parent tells its children the requirements, we end up with our goal achieved:
Each view’s measured size includes its children size, and fit its parent requirements.
Step 2: Layout
Goal: Set position and size (drawing width & drawing height) for view and all its descendants.
Similar to step 1: top-down recursive traversal of the view tree.
Each parent positions all of its children according to sizes measured on previous step.
The positioning is done on method onLayout(boolean changed, int left, int top, int right, int bottom) whereas left, top, right and bottom are relative to parent.
When overriding onLayout(), we have to call layout() on each child.
Step 3: Draw
After size and position is figured out, the view can draw itself accordingly.
In onDraw(Canvas) Canvas object generates (or updates) a list of OpenGL-ES commands (displayList) to send to the GPU.
So this is about it! But what happens when we change view’s properties? due to animation, or user input, or if we decided to change them?
When things change…
When view properties change, the view notifies the system. Depending on the changed properties, the view calls either:
Invalidate — which calls onDraw() for only the view
requestLayout — which bubbles up to the root view, then calls the entire process (measure → layout → draw)
A classic example for a situation that requests a layout: Let us we have 2 views located relatively to one another within a RelativeLayout. Than, if one view changes its size — it must result the other view to reposition, and maybe the parent to change size. So we changed one view’s properties, but it caused the whole layout to be outdated.
Situations like these reminds us that it’s important to have efficient layouts, so the layout will be executed smoothly and won’t cause skipping frames.
We now better understand how does Android measure and layout our views.
But in this process things might not always be so simple. Next post will discuss another situation that might affect badly on our layouts performance, and notes some ways to handle it.