认识一个Activity界面结构
每个Activity都会有一个Window,Window是抽象类具体由PhoneWindow实现。PhoneWindow中有一个顶级View即DecorView,DecorView是一个FrameLayout,有唯一的子view垂直布局的LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(android.R.id.content),我们平常用的setContentView就是设置它的子View。
Activity界面结构.pngRootView
View的测量(measure),布局(layout),绘制(draw)的三大流程都由RootView完成,RootViewImpl是RootView的具体实现类。在Activity被创建的完成后,会将DecorView添加到Window中,同时创建RootViewImpl对象,并将RootViewImpl与DecorView建立关联。View真正的绘制流程是从performTraversals开始的。
View的绘制流程
View的绘制流程.pngMeasure测量过程
在measure测量阶段:起点是ViewRootImpl的measureHierarchy()方法---->在measureHierarchy中会执行到performMeasure(childWidthMeasureSpec, childHeightMeasureSpec),传入performMeasure()方法的MeasureSpec的SpecMode为EXACTLY,SpecSize为窗口尺寸---->在performMeasure方法中调用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec),mView就是DecorView---->对于decorView来说,实际执行测量工作的是FrameLayout的onMeasure()方法。在onMeasure中调用measureChildWithMargins()方法对所有子View进行了一遍测量,并计算出所有子View的最大宽度和最大高度。
对于ViewGroup来说,它会调用child.measure()来完成子View的测量。传入ViewGroup的MeasureSpec是它的父View用于约束其测量的,那么ViewGroup本身也需要生成一个childMeasureSpec来限制它的子View的测量工作。这个childMeasureSpec就由getChildMeasureSpec()方法生成,childMeasureSpec的形成规律如下:
childMeasureSpec的形成.png
View的measure过程由measure方法来完成,measure方法是一个final类型的方法,子类不能重写此方法。在View的measure方法中会去调用view的onMeasure方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension会设置view测量的宽高。注意这里是测量的宽高,并非最终的宽高,虽然绝大数情况下测量宽高和最终宽高一样,但是最终的宽高还是由layout来决定。getMeasuredWidth(),getMeasuredHeight()方法用来获得测量的宽高,通过这两个方法在onMeasure中不一定能获得准确的测量宽高,因为有时候或多次调用onMeasure,所以在onLayout中去获得测量宽高是最保险的方式。
Layout布局过程
layout确定View本身的位置,onLayout确定子元素的位置
下面是view中layout的源码:先是通过setFrame的方法来确定view自己的位置,然后再onLayout的方法来确定子view的布局,onLayout没有统一的实现,不同的viewGroup由不同的实现。
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
getMeasureHeight()和getHeight(),测量宽高和最终宽高的区别
getMeasureHeight()是在measure之后获得,getHeight()是在layout之后获得的,两个绝大多数情况下是一样的,只有重写layout方法之后会不同。在下面这种情况下,getHeight比getMesaureHeight大100
public void layout(int l, int t, int r, int b) {
super.layout(l, t, r+100, b+100);
}
draw绘制流程
实际上,View类的onDraw()方法为空,因为每个View绘制自身的方式都不尽相同,对于decorView来说,由于它是容器View,所以它本身并没有什么要绘制的。dispatchDraw()方法用于绘制子View,显然普通View(非ViewGroup)并不能包含子View,所以View类中这个方法的实现为空。ViewGroup类的dispatchDraw()方法中会依次调用drawChild()方法来绘制子View。
draw过程简单遵循如下几步:
(1)绘制背景backgroud.draw(canvas)
(2)绘制自己(onDraw)
(3) 绘制children(dispatchDraw)
(4)绘制装饰 onDrawScrollBars
网友评论