、、、
}
3、FrameLayout的onMeasure方法
注释1处先求出子view个数;
在注释2处表示循环遍历每一个子view然后执行measureChildWithMargins;
测量完所有子view之后在注释3处设置自身的尺寸;
注释4和注释5表示会对所有设置MatchParent属性的子view重新measure,因为MatchParent属性比较特殊,刚开始并不知道父view的尺寸,所以需要重新测量;
我们继续看一下注释2处是怎么测量子view的,FrameLayout并没有measureChildWithMargins方法,而是在父类ViewGroup中定义的,我们接着看一下ViewGroup的measureChildWithMargins方法;
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount(); //1
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0,heightMeasureSpec,0); //2
}
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT)); //3
count = mMatchParentChildren.size(); //4
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
、、、
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //5
}
}
}
4、ViewGroup的measureChildWithMargins方法
注释1处先求出子view的LayoutParams布局参数;
然后在注释2和注释3处通过父view的MeasureSpec和子view的布局参数确定子view的MeasureSpec;
最后在注释4处又调用了child.measure(childWidthMeasureSpec, childHeightMeasureSpec);这样又会递归调用到View的measure方法,在measure方法中又会调用onMeasure方法,如果这个子view是一个viewgroup类型的,则又会递归调用到该viewgroup的onMeasure方法;直到递归调用到最下层view时,则会调用到View的onMeasure方法;
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); //1
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
- widthUsed, lp.width); //2
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin - heightUsed, lp.height); //3
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); //4
}
5、View的onMeasure方法
注释1处的setMeasuredDimension方法设置自身的尺寸,通过getDefaultSize获取尺寸;
注释3和注释4将mMeasuredWidth和mMeasuredHeight赋值为我们测量后的值;因此measure流程结束我们就可以通过getMeasuredWidth和hgetMeasuredeight获取尺寸了,比如我们在onLayout方法中就会可以调用;
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); //1
}
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
、、、
setMeasuredDimensionRaw(measuredWidth, measuredHeight);//2
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth; //3
mMeasuredHeight = measuredHeight; //4
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
6、View的getDefaultSize方法
注释1处获得specMode;
从注释2处我们可知,如果我们自定义view直接继承自View,那么我们不管设置wrap_content还是match_parent效果都是一样的;
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec); //1
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY: //2
result = specSize;
break;
}
return result;
}
三、布局流程layout
1、performLayout方法
布局流程的入口函数就是performLayout,如下所示,注释1处将decorView赋值给host,然后在注释2处执行host的layout,也就是DecorView的layout,DecorView、DecorView的父类FrameLayout、FrameLayout的父类ViewGroup都没有重写layout函数,因此实际调用的是View的layout,这块跟measure的逻辑是一样的;
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mInLayout = true;
final View host = mView; //1
if (host == null) {
return;
}
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //2
mInLayout = false;
}
2、View的layout方法
在View的layout方法中其实主要就是调用了onLayout,DecorView重写了onLayout,而在DecorView的onLayout中又调用了super.onLayout(changed, left, top, right, bottom);也就是FrameLayout的onLayout方法;
注释1处的setFrame方法主要是给当前view设置尺寸,具体实现是在注释2、3、4、5处分别给左上右下赋值;所以执行完layout过程后就可以通过getWidth和getHeight获得view的宽高了,我们一般在onDraw方法中调用这两个方法获取view的宽高;
public void layout(int l, int t, int r, int b) {
、、、
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //1
onLayout(changed, l, t, r, b);
、、、
}
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
// Invalidate our old position
invalidate(sizeChanged);
、、、
mLeft = left; //2
mTop = top; //3
mRight = right; //4
mBottom = bottom; //5
、、、
return changed;
}
3、FrameLayout的onLayout方法
注释1处onLayout又调用了layoutChildren;
注释2处计算出所有子view的数量;注释3处循环遍历每一个子view,并执行子view的layout方法;因此又会执行到View的layout方法,进而又会执行到onLayout方法;如果子view又是一个viewgroup,那么还会循环执行上述操作;如果子view是一个具体的view,就是调用到view的onLayout,其实view的onLayout是一个空实现;尺寸的赋值在第2步已经介绍了;
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */); //1
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount(); //2
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
、、、
child.layout(childLeft, childTop, childLeft + width, childTop + height); //3
}
}
}
四、绘制流程draw
1、performDraw
方法的调用链如下边注释1、2、3所示,最终会调用到View的draw方法;
private void performDraw() {
、、、
boolean canUseAsync = draw(fullRedrawNeeded); //1
、、、
}
private boolean draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) { //2
return false;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
、、、
mView.draw(canvas); //3
、、、
}
2、View的draw方法
在view的draw方法中官方也给出了注释,注释中表示draw流程分为7步,在注释中也阐述的非常清楚了,我们不再重复,每一步都可以点进去对应的方法查看,我们主要看一下第三四步;
第3步是通过onDraw方法绘制自身,我们点进去发现是一个空实现,所以需要在具体的view中自己去实现;
第4步通过dispatchDraw(canvas)方法去绘制所有的子view,在View中是一个空实现,在viewgroup中给出了具体的实现;
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)
-
7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we’re done…
return;
}
}
3、ViewGroup中的dispatchDraw方法
注释1表示在dispatchDraw方法中调用drawChild来绘制子view,注释2表示又会调用每个child的draw方法,这样层层递归最终绘制出整个view;
protected void dispatchDraw(Canvas canvas) {
final int childrenCount = mChildrenCount;
、、、
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime); //1
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
}
、、、
}
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime); //2
}
五、注意点:
requestLayout和invalidate以及postInvalidate的区别
1、requestLayout
在view中调用requestLayout后,会递归调用parent的requestLayout,最终会调用到ViewRootImpl的requestLayout方法,如注释1所示,最终调用到performTraversals执行三大流程,因为requestLayout将ForceLayout标志位置为true,因此onMeasure和onLayout肯定会执行,onDraw会根据视图的左上右下参数以及是否是脏数据来决定是否重新执行;
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
最后
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。
因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
pUKG5cmp-1715491366304)]
[外链图片转存中…(img-sdMM4XqE-1715491366306)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门
如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!