上一篇文章我们了解了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
- 4.View绘制分析笔记之onDraw
上一篇文章我们了解了View的onLayout,那么今天我们来学习Android View绘制三部曲的最后一步,onDraw,绘制. ViewRootImpl#performDraw private ...
- 2.View绘制分析笔记之onMeasure
今天主要学习记录一下Android View绘制三部曲的第一步,onMeasure,测量. 起源 在Activity中,所有的View都是DecorView的子View,然后DecorView又是被V ...
- 1.Android 视图及View绘制分析笔记之setContentView
自从1983年第一台图形用户界面的个人电脑问世以来,几乎所有的PC操作系统都支持可视化操作,Android也不例外.对于所有Android Developer来说,我们接触最多的控件就是View.通常 ...
- Android笔记--View绘制流程源码分析(二)
Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...
- Android笔记--View绘制流程源码分析(一)
Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...
- Android应用层View绘制流程与源码分析
1 背景 还记得前面<Android应用setContentView与LayoutInflater加载解析机制源码分析>这篇文章吗?我们有分析到Activity中界面加载显示的基本流程原 ...
- Android中View绘制流程以及invalidate()等相关方法分析
[原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...
- Android之View绘制流程源码分析
版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...
- Android中View绘制流程以及invalidate()等相关方法分析(转)
转自:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...
随机推荐
- SpringMVC拦截器的使用
SpringMVC 中的Interceptor 拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理.比如通过它来进行权限验证,或者是来判断用户是否登陆,或者是像12306 那 ...
- 常用function() 收集
1.随机数生成函数(来源-微信支付demo案例) /** * * 产生随机字符串,不长于32位 * @param int $length * @return 产生的随机字符串 */ public st ...
- 关于JSP中<body onload="fun()">body标签中onload中函数不执行问题
问题描述: 在一个页面中,我们经常会初始化一下数据,而且会在指定的DOM元素初始化数据,这时候我们就会使用<body onload="fun()">来加载我们的数据.o ...
- runtime-对成员变量和属性的操作
成员变量 首先我们来看看成员变量在runtime中是什么样的 在runtime中成员变量是一个objc_ivar类型的结构体,结构体定义如下 struct objc_ivar { char *ivar ...
- C#中POST数据和接收的几种方式(抛砖引玉)
POST方式提交数据,一种众所周知的方式: html页面中使用form表单提交,接收方式,使用Request.Form[""]或Request.QueryString[" ...
- 【linux使用】bash shell命令行常用快捷键 (转载)
移动: Ctrl + A: 移动到当前编辑的命令行首, Ctrl + E: 移动到当前编辑的命令行尾, Ctrl + F 或 ->:按字符右移(往命令行尾部方向,前移) Ctrl + B 或 & ...
- php php-5.6.4.tar.bz2 apache 兼容问题 child pid 27858 exit signal Segmentation fault
环境 [root envirotar]# uname -a Linux i2..el6.x86_64 # SMP Thu Jul :: UTC x86_64 x86_64 x86_64 GNU/Lin ...
- jvm--3.内存管理
5.JVM内存管理 JAVA虚拟机在执行java程序的过程中,会把它管理的内存分成若干个不同的数据区域. ----------------------------------------------- ...
- 带参方法的执行:普通方法的查询,可为空方法的查询。批量处理SQL语句。
普通方法的查询: @Override public List<Map<String, Object>> selectSpentAmount(Integer MAT_TYPE_, ...
- C#微信公众号开发系列教程二(新手接入指南)
http://www.cnblogs.com/zskbll/p/4093954.html 此系列前面已经更新了两篇博文了,都是微信开发的前期准备工作,现在切入正题,本篇讲解新手接入的步骤与方法,大神可 ...