Overview
After an activity is launched, created, and started, the view hierarchy inside is created in memory, and ready to be drawn; the drawing starts very from ActivityThread#handleResumeActivity()
The drawing of the hierarchy is sequentialized to three passes:
- measure: this pass measures the size (width, height) of each view in hierarchy according to the each view’s layout params and parent measurement specs; in this pass,
View#onMeasure()
is invoked; after this pass, getMeasured{Width, Height}()
of View (along with all its children) should return correctly
- layout: after measured, the view is calculated to where it should be put on the screen (left, top, width, height), according to the device configuration, like screen size, orientation, etc.; in the process,
View#onLayout()
is invoked; after this pass, get{Left, Top, Width, Height, Right, Bottom}()
(along all its children) should return correctly
- draw: after layout, the view is actual ready to be drawn to the screen; but according to whether the actual drawing is accomplished via GPU or not, this pass is classified to software drawing (swdraw) and hardware accelerated drawing (hwdraw), and after Android 4.+, hwdraw is set as the default; in this pass,
View#onDraw()
is invoked
- Swdraw is performed by Skia (actually, skia can do hwdraw, but Google only use it to do swdraw) directly draws the view to the screen synchronously, in implementation, it directly invokes
View#draw()
recursively
- Hwdraw is performed via OpenGL (actually, Google prepared to abandon OpenGL and use Skia to do hwdraw). Hwdraw only requires the hierarchy to emit its draw operations one by one. In detail, hwdraw abstracts each view in the hierarchy as a
RenderNode
, and the drawing performed on them are DrawOp
s they emit; the hierarchy is then formed to a render node tree, and operations a draw operation list; therefore, in this pass, View#onDraw()
is invoked only to emit draw operations but does not actual draw, and it is the render thread's responsibility to sync these operations to OpenGL, and OpenGL will render them via GPU. The following image presents the pass.
In implementation, ViewRootImpl#performTraversal()
performs all the three passes recursively by traversing the hierarchy top-down
References
ViewRootImpl#performMeasure()
The measure pass measures the measured height and width of each view in the hierarchy, therefore after measurement, the measured height and width (View#getMeasured{Width, Height}()
)of each view should be set correctly, or an IllegalStateException
is thrown
Actually, performMeasure()
only invokes the measure()
method of the view it manages (in practice, it is the decor view), therefore, for a successful measurement, all ViewGroup
s must invoke measure()
of its children recursively
View#measure()
is called to find out how big a view should be, but the actual measurement is performed by View#onMeasure()
, and measure()
does caching, and checks whether measured height and width are set
Generally, every ViewGroup
if it has children must override onMeasure()
to recursively invoke its children’s measure() method
View#onMeasure()
Height and width of each view comes from both (1) restrictions from its parent (MeasureSpec
), and (2) wishes from its users (LayoutParams
)
MeasureSpec
: it is restrictions from its parent in the layout tree, this spec tells the view which restrictions should be satisfied (e.g., the parent may tell its children the most width it can be)
LayoutParams
: it is the wishes from its user (set in xml or by LayoutParams
), e.g., the user may tell that he wants the view to match the width of its parent, or to be a fixed value like 100px
Therefore, View#onMeasure(widthMeasureSpec, heightMeasureSpec)
always judges the users wishes, and adjusts them to satisfy the parent’s restriction; and the values after adjusted is the measured values
- e.g., the
onMeasure()
method of LinearLayout
(vertical) should divide the height and generate the MeasureSpecs
for each of its children
See View#{onMeasure, getDefaultSize}()
for more details
Root Spec
The root MeasureSpec
set for the view managed by ViewRootImpl
comes from the LayoutParams
of the window it communicates to
ViewRootImpl#getRootMeasureSpec()
generates that, in detail
- it generates an EXACTLY
MeasureSpec
(equals to width of the window) for a MATCH_PARENT LayoutParams
- it generates a fixed-value
MeasureSpec
(equals to width of the window) for a fixed-value LayoutParams
- it generates an AT_MOST
MeasureSpec
(at most width of the window) for a WRAP_CONTENT LayoutParams
See ViewRootImpl#getRootMeasureSpec()
for more details
ViewRootImpl#performLayout()
The measure pass sets a size and position to each view in the hierarchy, therefore after layout, the left, top, height and width (View#get{Left, Top, Width, Height, Right, Bottom}()
)of each view should be set correctly
Actually, performLayout()
only invokes the layout(t, l, r, b)
method of the view it manages (in practice, it is the decor view), therefore, for a successful layout, all ViewGroups
must invoke layout() of its children recursively
View#layout() is where the actual layout is performed, it is called to set position and size of a view. layout() additionally checks whether a relayout is needed to improve performance, and if so, callback onLayout() to inform the view
Generally, every ViewGroup
if it has children must override onLayout()
to recursively invoke its children’s layout()
method
View#layout()
layout()
is where the actual layout is performed, it is called to find out to where a view is put and how big a view actually be.
layout()
by default directly sets the measured width/height as the width and height of the view
Additionally, layout()
checks whether a relayout is needed to improve performance, and if so, callback View#onLayout()
to notify the view (group to relayout its children)
Root Left/Top and Width/Height
The root left/top and width/height comes from the DecorView
, it is the 0
/0
and decorView.getMeasured{Width, Height}()
The reason for left/top being 0/0 is that the mLeft
/mTop
/mRight
/mBottom
of a View
is a relative value to its parent, and DecorView
located at (0, 0) of the window
ViewRootImpl#performDraw() and View#draw()
ViewRootImpl#performDraw()
invokes the draw() of its managed view
The View#draw()
method does many works in a specific order:
- draw the background (the android:background in xml)
- if necessary, draw the canvas’ layers to prepare for fading (animation)
- draw the view’s content by callback to View#onDraw(), this is where the draw is actually happened
- draw its children; one can see from here, that ViewGroup draw its children in draw, and the draw method is not supposed to override
- if necessary, draw the fading edges (animation)
- draw decorations, e.g., the scrollbar
View#onDraw(canvas)
is where draw actually happened
Generally, every View
if it has something special should override onDraw()
to emit draw commands
Summary
To implement a custom ViewGroup
:
- It is responsible to override
onMeasure()
and onLayout()
to manage the measure and layout of its children, by recursively invoking its children’s measure()
or layout()
method, for them to show at the correct position, and have the right size;
- it does not need to recursively call anything for its children to draw, because the
draw()
method itself already does that; if it has something special to draw, can override onDraw()
and draw that
To implement a custom View
:
- if it has something special to draw, override
onDraw()
and draw that
- it usually does not need to override
onMeasure()
and onLayout()
, the default version is sufficient for them; but if it has some special positions, it needs that
The default View#onMeasure()
(actual measurement) sets the measured values to be the minimum one, however, the default View#onLayout()
(just notifier, but can also do layout) and View#onDraw()
(actual draw) does nothing because there’s nothing to be notified and drawn
References