博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
3.View绘制分析笔记之onLayout
阅读量:6861 次
发布时间:2019-06-26

本文共 6364 字,大约阅读时间需要 21 分钟。

上一篇文章我们了解了View的onMeasure,那么今天我们继续来学习Android View绘制三部曲的第二步,onLayout,布局。

ViewRootImpl#performLayout

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(mTag, "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; //此处省略的代码是在layout的过程中,重复的requestLayout,需要做的处理。 //具体的处理方案是重新measure,layout。 ... } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }

这个方法主要的作用就是调用了host.layout,并把已经测绘好的宽高传计算成上下左右递过去,host就是decorView。

View#layout

public void layout(int l, int t, int r, int b) {
//根据mPrivateFlags3标记位状态判断,如果需要,则重新measure。 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; //检查是位置有变化,并setFrame //setFrame分析见下文 boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //如果位置有变化或者PFLAG_LAYOUT_REQUIRED标记位为on,则进行onLayout if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); //把PFLAG_LAYOUT_REQUIRED标记位置为off mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; //进行onLayoutChange回调 ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList
listenersCopy = (ArrayList
)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); } } } //将PFLAG_FORCE_LAYOUT标记置为off,将PFLAG3_IS_LAID_OUT置为on mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }

View#setFrame

protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false; if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } //如果上下左右任意一项有改动,则继续往下进行,否则直接返回false if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; //记录PFLAG_DRAWN位状态,最后复原的时候需要 int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); //刷新原有布局,invalidate方法将在另一篇文章中详细展开。 invalidate(sizeChanged); //设置该View的上下左右,也是setFrame的核心功能 mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); //PFLAG_HAS_BOUNDS位置为on mPrivateFlags |= PFLAG_HAS_BOUNDS; //如果尺寸有改变,调用onSizeChange并且调用rebuildOutline if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child invalidateParentCaches(); } // 把PFLAG_DRAWN设置为原有数值。(invalidate过程中会将其设为off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } //Android无障碍辅助通知 notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; }

FrameLayout#onLayout

如果是View的话,执行完layout方法,那么他已经布局完成,不过如果是ViewGroup,那么它需要对它的子View进行处理。onLayout主要的作用就是调用layoutChildren,对子View进行布局,所以这里着重介绍layoutChildren。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount(); //计算parent的上下左右 final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); 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; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } //获取layout默认方向,通常是从左到右,在某些特定语言的情况下是从右到左 final int layoutDirection = getLayoutDirection(); //通过刚才的方向值,计算出绝对的横向位置属性 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); //计算竖向位置属性 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; //通过位置属性,计算子View的left和right switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } //通过位置属性,计算子View的top和bottom switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } //调用子View的layout方法 child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }

时序图

图为View layout 时序图

小结

到这里就介绍完了View绘制的layout方法。比起measure,layout可是简单多了。不过这里还预留了一些坑,没有交代清楚,比如invalidate,还有RenderNode硬件加速等,以后会写一些笔记专门针对这些知识点做梳理。

系列文章

转载地址:http://fpqyl.baihongyu.com/

你可能感兴趣的文章
使用Django操作数据库入门
查看>>
ny106 背包问题
查看>>
nyoj228 士兵杀敌(5)插线问线
查看>>
ny712 探寻宝藏 ny61 传纸条(1)
查看>>
CSS后代选择器可能的错误认识
查看>>
Python垃圾回收机制
查看>>
Gson将参数放入实体类中进行包装之后再传递
查看>>
设置mysql5.7远程连接-----------https://blog.csdn.net/qiyueqinglian/article/details/52778230
查看>>
IOS7状态栏StatusBar官方标准适配方法
查看>>
嵌入式开发之项目---uboot 内存合集
查看>>
模式识别之ocr项目---(模板匹配&BP神经网络训练)
查看>>
python3 geohash 导入错误及解决
查看>>
zabbix告警使用sendEmail
查看>>
CCF-NOIP-2018 提高组(复赛) 模拟试题(七)
查看>>
Java 基础知识点小结
查看>>
博弈入门
查看>>
iOS中获取当前时间,设定时间,并算出差值
查看>>
适配 移动 pc 拖拽效果
查看>>
a threadpool by python
查看>>
Callable 与 Future
查看>>