View绘制过程,首页第一步了解整个android的view框架,这其中涉及到几个基本的概念。
Activity:基本的页面单元,承载着整window,view实际上是绘制在这个window之上的。
View:最基本的UI组件,可以的布局中直接使用。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource
实现了Drawable,和KeyEvent的回调方法
Window:window是一个抽象类,表示一个顶层对象。用来管理界面和响应事件,每个activity均会创建一个
PhoneWindow:继承自window对象,是整个activity与view的交互接口,该类中包含一个
DecorView,DecorView继承自FrameLayout
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker;
也就是说任何activity的window的rootView都是一个DecorView,实际上,DecorView是在framelayout中添加一点东西而已,包括一个通用的titleBar,
所以我们在布局的时候不设置requestWindowFeature的时候默认会有一个titlebar
ViewRoot:很抱歉,在最新得framework代码中并没有找到这个类,在最新的framework中
ViewRootImpl这个类,整个view的绘制起点就是此处
整个基础框架大致是如此。
view的绘制过程会中
ViewRootImpl的doTraversal()方法中开始,此方法会调用view的measure方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在此方法中会执行performTraversals()方法。我们在famework中继续了解此方法:
这个方法代码较多,基本逻辑是判断是否有位移变化,是否有可见性变化,以及是否需要重新绘制,请求大小,位置,和重绘等
if (!mStopped) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//获取
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(TAG,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
}
可以看到在此处会执行
performMeasure,请求计算view的大小。接下来我们看到view的measure方法
measure接受两个参数widthMeasureSpec,heightMeasureSpec。
这两个参数由
ViewRootImpl中的
getRootMeasureSpec中获取
方法
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
}
onMeasure(int widthMeasureSpec,int heightMeasureSpec)
measureSpec由specSie和specMode组成
specMode有三个值:
EXACTLY:父视图希望子视图的大小是由specSize决定 height = heightSize;
EXACTLY:父视图希望子视图的大小是由specSize决定 height = heightSize;
AT_MOST:不超过specSize,一般在计算完理论大小的时候执行Math.min(width,specSize);
UNSPECIFIED:
在这个方法最后调用setMeasuredDimension(width, height);
onLayout()
在ViewRootImpl中的1844行中会调用
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
中会调用view的layout(0,0,getMeasureWidth(),getMeasureHeight);
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); //判断整个view的大小是否发生变化
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;
}
在view的layout方法中最后会回调
onLayout(changed, l, t, r, b);以及
onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);此处是一个监听回调函数,可以为空
在view的onLayout方法中并没有做什么操作,所以view最后的坐标会是0,0,getMeasureWidth(),getMeasureHeight();在此处可以研究一下viewgroup的onlayout方法
因为view在位置并不是自己决定的,而是由viewgroup的onlayout方法决定的,viewgroup中的onlayout是一个抽象方法,所以自定义viewgroup一定要实现onlayout方法
所以我们可以这么理解,在measure结束后整个view的大小是确定了,在onlayout方法中要确定的是view的一个位置。
最后我们会进入到ondraw方法,ondraw方法实际上是对整个view的一个绘制过程。
在最新版本的framework代码中,view的ondraw是一个空的方法
protected void onDraw(Canvas canvas) {
}
google为我们提供了一个绘制顺序:
1、绘制背景
2、如果有必要保存canvas的一些属性,保存画层
3、绘制view的content。
4、绘制view的childen
5、如果有必要会边缘的渐变的内容
6、绘制滚动条