共计 6396 个字符,预计需要花费 16 分钟才能阅读完成。
PS:本文系转载文章,浏览原文可读性会更好,文章开端有原文链接
ps:文章是基于 Android Api 31 来剖析源码的。
目录
1、View 的 draw 过程
1、1 View(它不是 ViewGroup)的 draw 过程 | |
1、1、1 原始 View 的 draw 过程 | |
1、1、2 具体 View 的 draw 过程 | |
1、2 ViewGroup 的 draw 过程 | |
1、View 的 draw 过程
这里 View 的 draw 过程就是 View 的绘制过程,也就是将 View 显示在屏幕上,这里的 View 是一种是指 ViewGroup,一种是指原始的 View 或者是 View 的子类(更具体的 View)但不是 ViewGroup。
1、1 View(它不是 ViewGroup)的 draw 过程
1、1、1 原始 View 的 draw 过程
假如咱们的 xml 布局文件有一个 View 标签,如下所示;
图片
如果咱们把下面的代码运行一下,在屏幕上就会显示一个红色的矩形对不对?,好,咱们当初看它的实现过程,咱们看一下 View 的 draw(Canvas canvas) 办法;
图片
看正文 1,是绘制 View 的背景,看咱们 xml 布局中的 View 标签,是不是增加了一个背景为 #FF0000 的色彩值呢?它代表了红色;正文 4 示意绘制 View 的装璜,比方前景和滚动条;正文 2 示意绘制 View 的内容,那咱们看看 View 的 onDraw(Canvas canvas) 办法的具体实现;
图片
看到没,View 的 onDraw(Canvas canvas) 办法是空实现,也就是 View 标签不绘制内容,如果咱们比设置 background 属性的话,咱们只能看到一个通明的 View。
看正文 3,它示意绘制 View 的子元素,咱们再看 View 的 dispatchDraw(Canvas canvas) 办法的具体实现;
图片
看到没有,View 的 dispatchDraw(Canvas canvas) 办法也是一个空实现,也就是说 View 标签上面是不会存在子元素的。
1、1、2 具体 View 的 draw 过程
这里所说的具体 View 是指继承了 View 的子类,常见的零碎具体 View 有 TextView、ImageView、EditText 等,上面咱们就以 TextView 为例,剖析 TextView 的 draw 过程;TextView 没有重写 View 的 draw(Canvas canvas) 办法,确重写了 View 的 onDraw(Canvas canvas) 办法,该办法是绘制本人的内容;
@Override | |
protected void onDraw(Canvas canvas) { | |
...... | |
//5、final int compoundPaddingLeft = getCompoundPaddingLeft(); | |
final int compoundPaddingTop = getCompoundPaddingTop(); | |
final int compoundPaddingRight = getCompoundPaddingRight(); | |
final int compoundPaddingBottom = getCompoundPaddingBottom(); | |
//6、final int scrollX = mScrollX; | |
final int scrollY = mScrollY; | |
//7、final int right = mRight; | |
final int left = mLeft; | |
final int bottom = mBottom; | |
final int top = mTop; | |
...... | |
//8、final Drawables dr = mDrawables; | |
if (dr != null) { | |
/* | |
* Compound, not extended, because the icon is not clipped | |
* if the text height is smaller. | |
*/ | |
//9、int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; | |
//10、int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; | |
// IMPORTANT: The coordinates computed are also used in invalidateDrawable() | |
// Make sure to update invalidateDrawable() when changing this code. | |
if (dr.mShowing[Drawables.LEFT] != null) { | |
//11、canvas.save(); | |
...... | |
//12、canvas.restore();} | |
...... | |
} | |
...... | |
//13、if (mLayout == null) {assumeLayout(); | |
} | |
...... | |
//14、if (mHint != null && mText.length() == 0) {if (mHintTextColor != null) {color = mCurHintTextColor;} | |
layout = mHintLayout; | |
} | |
...... | |
//15、float clipLeft = compoundPaddingLeft + scrollX; | |
float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; | |
float clipRight = right - left - getCompoundPaddingRight() + scrollX; | |
float clipBottom = bottom - top + scrollY | |
- ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); | |
//16、if (mShadowRadius != 0) {clipLeft += Math.min(0, mShadowDx - mShadowRadius); | |
clipRight += Math.max(0, mShadowDx + mShadowRadius); | |
clipTop += Math.min(0, mShadowDy - mShadowRadius); | |
clipBottom += Math.max(0, mShadowDy + mShadowRadius); | |
} | |
//17、canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); | |
...... | |
//18、if (mEditor != null) {mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); | |
} else {layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); | |
} | |
...... | |
} | |
正文 5 示意获取上下左右的 padding 值;正文 6 示意获取 X 轴和 Y 轴上的 scroll 值;正文 7 示意获取上下左右 4 个顶点的地位;正文 8 示意如果 Drawables 不为空,那么就绘制 Drawables;正文 9 示意计算垂直方向的空间;正文 10 示意计算程度方向的空间;正文 11 中的 save 办法调用之后,能够对 canvas 进行平移和旋转,确定新的原点而后绘制,等绘制完了之后,能够把原点恢复原状;正文 13 示意如果 layout 为 null,通过 makeNewLayout 办法,再去取得一个 layout;正文 14 示意如果以后没有文字,并且设置了 hint 属性,那么就显示 hint 属性的文字;正文 15 示意计算矩阵的上下左右 4 个坐标值;正文 16 示意解决文字暗影;正文 17 示意在画布中裁剪出刚计算出来的矩阵大小;正文 18 示意如果是 EditText 的就交给 mEditor 绘制。
1、2 ViewGroup 的 draw 过程
Android 中零碎自带的 ViewGroup 子类常见的有 RelativeLayout、LinearLayout、GridLayout、TableLayout、FrameLayout 和 Constraint-Layout,这里咱们就以 FrameLayout 为例,剖析 FrameLayout 的 draw 过程;剖析这个 FrameLayout 的 draw 过程之前,咱们先看一下 View 中一个很有意思的办法,那就是 setWillNotDraw(boolean willNotDraw) 办法,咱们看一下该办法的源码;
图片
当 willNotDraw 值为 trure 的时候,View 的 setFlags 办法的第一个参数就为 WILL_NOT_DRAW,当 willNotDraw 值为 false 的时候,View 的 setFlags 办法的第一个参数就为 0;那这个 willNotDraw 代表什么含意呢?如果 willNotDraw 为 true 当前,以后这个 View 不须要绘制任何内容,零碎会进行相应的优化;默认状况下,View 会将 setFlags 办法的第一个参数置为 0,也就是要绘制以后 View;然而 ViewGroup 会将 View 的 setFlags 办法的第一个参数设置为 WILL_NOT_DRAW,也就是不对这个 ViewGroup 进行绘制,不信的话咱们看看 ViewGroup 的其中一个构造方法;
图片
看正文 19,ViewGroup 的其中一个构造方法调用了 ViewGroup 的 initViewGroup 办法,咱们往下看 initViewGroup 办法;
图片
看到正文 20 的代码没有,ViewGroup 默认不对本身的内容进行绘制;如果咱们的自定义控件继承于 ViewGroup 并须要对自定义的 ViewGroup 进行绘制时,能够在自定义的 ViewGroup 的构造方法中调用 View 的 setWillNot-Draw(boolean willNotDraw) 办法,并将 willNotDraw 参数设为 false。
好,咱们回到剖析 FrameLayout 的 draw 过程,咱们晓得 FrameLayout 和 ViewGroup 都没有重写 draw 办法和 onDraw 办法,只有 ViewGroup 重写了 dispatchDraw(Canvas canvas) 办法,咱们看看该办法;
@Override | |
protected void dispatchDraw(Canvas canvas) { | |
...... | |
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) { | |
//21、more |= drawChild(canvas, transientChild, drawingTime); | |
} | |
...... | |
} | |
...... | |
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { | |
//22、more |= drawChild(canvas, child, drawingTime); | |
} | |
} | |
while (transientIndex >= 0) { | |
// there may be additional transient views after the normal views | |
final View transientChild = mTransientViews.get(transientIndex); | |
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || | |
transientChild.getAnimation() != null) { | |
//23、more |= drawChild(canvas, transientChild, drawingTime); | |
} | |
...... | |
} | |
if (preorderedList != null) preorderedList.clear(); | |
// Draw any disappearing views that have animations | |
if (mDisappearingChildren != null) { | |
final ArrayList<View> disappearingChildren = mDisappearingChildren; | |
final int disappearingCount = disappearingChildren.size() - 1; | |
// Go backwards -- we may delete as animations finish | |
for (int i = disappearingCount; i >= 0; i--) {final View child = disappearingChildren.get(i); | |
//24、more |= drawChild(canvas, child, drawingTime); | |
} | |
} | |
...... | |
} | |
看到正文 21、22、23、24 所在的代码没有,都是调用 ViewGroup 的 drawChild(Canvas canvas, View child, long drawingTime) 办法对不对?那它们有什么区别吗?答案是必定有的;看正文 21 的代码,ViewGroup 绘制短暂的子 View;正文 22 的代码示意绘制一般可见的子视图;正文 23 的代码示意绘制一般子视图之外可能存在的长期子视图;正文 24 的代码示意绘制正在变为不可见的有动画的子视图。
咱们往下看 ViewGroup 的 drawChild(Canvas canvas, View child, long drawingTime) 办法;
图片
这里的 child 就是 FramLayout 的子 View,ViewGroup 的 drawChild(Canvas canvas, View child, long drawingTime) 办法又调用了 View 的 draw(Canvas canvas, ViewGroup parent, long drawingTime) 办法,咱们往下看 draw(Canvas canvas, ViewGroup parent, long drawingTime) 办法;
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...... | |
if (transformToApply != null | |
|| alpha < 1 | |
|| !hasIdentityMatrix() | |
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) { | |
...... | |
if (!drawingWithDrawingCache) {if (drawingWithRenderNode) {......} else { | |
// Fast path for layouts with no backgrounds | |
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { | |
mPrivateFlags &= ~PFLAG_DIRTY_MASK; | |
//25、dispatchDraw(canvas); | |
} else { | |
//26、draw(canvas); | |
} | |
} | |
} else if (cache != null) {......} | |
...... | |
return more; |
}
如果 FrameLayout 的子 View 不须要绘制本身(子 View)那么就调用子 View 的 dispatchDraw 办法,如果 FrameLayout 的子 View 须要绘制本身的内容那么就调用子 View 的 draw(Canvas canvas) 办法。