在项目的开发的过程中,对于一般的需求我们使用Android提供的原生的空间就可以满足开发需求,但是当我们遇到一些特殊的需求需要我们自定义View的时候,需要开发人员实现测量,布局和绘制等操作,这些都依赖于我们对View绘制流程的理解和掌握
先看下Android的UI管理系统的层级关系:
PhoneWindow是Android系统中最基本的窗口系统,每个Activity会创建一个.PhoneWindow是Activity和View系统交互的接口.DecorView本质上是Framelayout,是Activity中所有View的祖先
目录
1.绘制的整体流程
2.MeasureSpec
3.Measure
4.Layout
5.Draw
当一个app启动的时候,会启动一个主Activity,Android系统会根据Activity的布局对他进行绘制.绘制从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View空间负责绘制自己,而Viewgroup还要负责告诉自己的子View开始进行绘制,视图树绘制可以分为三个步骤:分别是测量Measure ,布局 Layout ,绘制 Draw.
关于performTraversals()方法,可以去源码查看,在ViewRootImp.java文件中
核心的三个点:
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
MeasureSpec表示的是一个32位的整型值,他的高2位表示测量模式SpecMode,低30位表示某种测量模式下的规格大小SpecSize .
MeadureSpec 是View类的一个静态内部类,用来表示改如果测量这个View
核心代码 :
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/**
* 不指定测量模式
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* 精确测量模式
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* 最大值测量模式
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 根据指定的大小和模式创建一个MeasureSpec
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
*微调SpecMeasure的大小
*/
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
}
三种测量模式 :
UNSPECIFIED :不指定测量模式,父视图没有限制子视图的大小,子视图可以想要任何尺寸.开发中很少见
EXACTLY : 精准的测量模式,当该视图的width和height指定为具体的数值或者是match_parent时候生效,表示父视图已经决定子视图的大小,改模式下View的测量值就是SpecSize的值
AT_MOST :最大值模式,宽和高为wrap_content的时候,子视图可以是不超过父视图允许的最大尺寸内的任何尺寸
对DecorView来说,他的MeasureSpec是由窗口尺寸和其自身的LayoutParams共同决定,对于普通的View,他的MeasureSpec由父视图和MeasureSpec和自身的layoutParams来决定
Measure方法是用来计算View的实际大小的,通过前面的分析,我们知道页面的绘制流程是从performMeasure开始的
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
//核心代码
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
具体的测量操作是分发给ViewGroup的,由ViewGroup在他的measureChild方法中传递给子类.ViewGroup通过遍历自身所有的子View,并调用View的measure方法来完成测量 ,查看ViewGroup中源码,找到2个关键的方法:
/**
* 遍历测量ViewGroup中的所有子View
*/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
/**
* 测量指定View
*/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
下面看下View和ViewGroup中的measure方法,最终的测量时通过回调onMeasure方法实现的.这个通常都是View的子类自己实现的,我们可以通过这个方法来实现自定义View
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
........
/*
* 核心代码
*/
onMeasure(widthMeasureSpec, heightMeasureSpec);
.............
}
如果自定义测量过程,这个方法可以重写
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
如果没有重写这个方法,默认的会直接调用getDefaultSize来获取View的尺寸
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
确定View在父容器中的位置,它是由父容器获取子view的位置参数后,调用子view的layout方法,将获取的参数传入来实现确定位置的.
viewRootImp中的performLayout代码 :
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
............
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...............
}
View中的layout方法代码 :
public void layout(int l, int t, int r, int b) {
//.......
onLayout(changed, l, t, r, b);
....
}
onlayout方法 :
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
这是一个空方法,View的子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View的布局 ,我们看下ViewGroup中这个方法
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
可以看到,它里面实现了View的这个onLayout方法,并且设置为抽象方法,这样它的子类必须实现这个方法来确定包含的子View的绘制 ,我们找个ViewGroup的子类看看代码: 最常用的线性布局中的代码=>
/**
* 实现了ViewGroup中的onLayout方法,分水平和垂直分别给出了绘制子View的流程
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
Draw操作是用来将控件绘制在界面上,绘制的流程是从performDraw方法开始的,看下代码 :
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
} else if (mView == null) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded || mReportNextDraw;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
boolean usingAsyncReport = false;
if (mReportNextDraw && mAttachInfo.mThreadedRenderer != null
&& mAttachInfo.mThreadedRenderer.isEnabled()) {
usingAsyncReport = true;
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback((long frameNr) -> {
// TODO: Use the frame number
pendingDrawFinished();
});
}
try {
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// For whatever reason we didn't create a HardwareRenderer, end any
// hardware animations that are now dangling
if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
}
mAttachInfo.mPendingAnimatingRenderNodes.clear();
}
if (mReportNextDraw) {
mReportNextDraw = false;
// if we're using multi-thread renderer, wait for the window frame draws
if (mWindowDrawCountDown != null) {
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
Log.e(mTag, "Window redraw count down interrupted!");
}
mWindowDrawCountDown = null;
}
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.setStopped(mStopped);
}
if (LOCAL_LOGV) {
Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
if (mSurfaceHolder != null && mSurface.isValid()) {
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
} else if (!usingAsyncReport) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.fence();
}
pendingDrawFinished();
}
}
}
核心的代码:
boolean canUseAsync = draw(fullRedrawNeeded);
看下draw方法 :
private boolean draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
...
}
drawSoftware方法 :
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
....
mView.draw(canvas);
......
}
根据源码追踪,看到了ViewRootImp中最终会调用view的draw方法具体绘制每个具体的View,绘制分为六个步骤: 看下View中的代码,截取出主要的代码:
public void draw(Canvas canvas) {
..........
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
1.绘制背景
2.如果需要,保存canvas图层,为fading做准备
3.绘制view内容
4.绘制view的子view
5.如果需要,绘制view的fading边缘并回复图层
6.绘制view的装饰(例如滚动条)
*/
}
完成...