上一篇文章我们了解了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<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);
}
}
} //将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硬件加速等,以后会写一些笔记专门针对这些知识点做梳理。

系列文章

Android 视图及View绘制分析笔记之setContentView
View绘制分析笔记之onMeasure
View绘制分析笔记之onLayout
View绘制分析笔记之onDraw

3.View绘制分析笔记之onLayout的更多相关文章

  1. 4.View绘制分析笔记之onDraw

    上一篇文章我们了解了View的onLayout,那么今天我们来学习Android View绘制三部曲的最后一步,onDraw,绘制. ViewRootImpl#performDraw private ...

  2. 2.View绘制分析笔记之onMeasure

    今天主要学习记录一下Android View绘制三部曲的第一步,onMeasure,测量. 起源 在Activity中,所有的View都是DecorView的子View,然后DecorView又是被V ...

  3. 1.Android 视图及View绘制分析笔记之setContentView

    自从1983年第一台图形用户界面的个人电脑问世以来,几乎所有的PC操作系统都支持可视化操作,Android也不例外.对于所有Android Developer来说,我们接触最多的控件就是View.通常 ...

  4. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  5. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  6. Android应用层View绘制流程与源码分析

    1  背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...

  7. Android中View绘制流程以及invalidate()等相关方法分析

    [原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...

  8. Android之View绘制流程源码分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...

  9. Android中View绘制流程以及invalidate()等相关方法分析(转)

    转自:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...

随机推荐

  1. JavaScript 秘密花园 学习心得

    目的 记录一下学习心得,便于以后复习,内容是比较基础的...但是很多内容我还是不知道... 对象 对象使用和属性 1.JavaScript 中所有变量都可以当作对象使用,除了两个例外 null和dun ...

  2. synthesize的作用

    @synthesize是对属性的实现,实际上就是制定setter和getter操作的实例变量的名称   举个栗子: @synthesize array;  默认操作的实例变量和属性同名 @synthe ...

  3. Oracle的表锁死以及解锁

    Oracle的表锁死以及解锁 oracle 查看锁死的表,锁死的进程. select sess.sid, sess.serial#, lo.oracle_username, lo.os_user_na ...

  4. 如何查看/统计当前AD域控制器的活动用户?

    最近公司想知道某台AD域控制器上当前连接了多少活动用户? 此前个人只知道以下不是非常完善且统计起来比较麻烦的方法: 方法1:查看共享会话数.(不完全准确) 方法2:查看当前的DNS记录.(这种方法统计 ...

  5. Dom4J解析技术

    前面的话  本文主要讲解有关Dom4j技术和xpath配合下的优化!   目录:    为什么需要Dom4J    DOM4J怎么用    xpath怎么配合DOM4J 一  为什么需要Dom4J 一 ...

  6. Web 常用功能测试方法

    功能测试就是对产品的各功能进行验证,根据功能测试用例,逐项测试,检查产品是否达到用户要求的功能.常用的测试方法如下: 1. 页面链接检查:每一个链接是否都有对应的页面,并且页面之间切换正确. 2. 相 ...

  7. iOS 打开本地 其他应用程序(URL Types)

    iOS 打开本地其他应用程序(URL Types) /*前言废话:Xcode是神奇的,是我所见到的编译器中最为神奇的,如:它可以同时运行两个甚至更多Project到我们模拟器上,可以同时使用一个模拟器 ...

  8. [codevs1743]反转卡片

    [codevs1743]反转卡片 试题描述 [dzy493941464|yywyzdzr原创] 小A将N张卡片整齐地排成一排,其中每张卡片上写了1~N的一个整数,每张卡片上的数各不相同. 比如下图是N ...

  9. ms08-067漏洞--初识渗透测试--想必很多初学者都会遇到我文中提及的各种问题

    最近读了一本书--<<渗透测试实践指南>>,测试了书中的一些例子后,开始拿ms08-067这个经典的严重漏洞练手,实践当中遇到诸多问题,好在一一解决了,获益匪浅. 在谷歌搜索的 ...

  10. [正则表达式]PCRE环视功能

    设想一下这个问题,假设为了方便长串数字的阅读性,需要为其添加逗号作为分隔,需要怎么做呢? 2569836495 => 2,569,836,495 正则表达式的匹配通常是从左往右的,这导致无法使用 ...