上一篇文章我们了解了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工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...
随机推荐
- 回顾Spirng ioc 控制反转
Spring的IoC(控制反转) .DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC .DI这两个概念是模糊不清的,是很难理解的.结合网上对Spring Ioc的理解,回顾一下自 ...
- windows7下Wamp安装php扩展imagick(转)
ImageMagick是一套功能强大.稳定而且免费的工具集和开发包,可以用来读.写和处理超过185种基本格式的图片文件,包括流行的TIFF, JPEG, GIF, PNG, PDF以及PhotoCD等 ...
- Retrieving the COM class factory for component with CLSID {000209FF-0000-0000-C000-000000000046} failed due to the following error: 80070005 拒绝访问。
这几天在写一个导出word的功能,使用 Microsoft.Vbe.Interop.dll和Office.dll 在本地都可以正常运行,但是上传到服务器后就报错,如下图: 对于此问题,也在网上查了一些 ...
- [译]How to Write a Git Commit Message
原文: http://chris.beams.io/posts/git-commit/ 介绍:为什么好的commit message很重要 你浏览项目commit message的时候或多或少会有些困 ...
- file xxx from install of xxx conflicts with file from xxx
执行安装 rpm -ivh lib64stdc++6-4.6.1-2-mdv2011.0.x86_64.rpm 时提示以下错误: warning: lib64stdc++6-4.6.1-2-mdv20 ...
- Azure上的那些IP
相信第一次接触Azure的读者都会碰到这样一个问题,就是Azure的IP地址,笔者第一次接触Azure也是被搞懵逼了,一会儿VIP,不知道的还以为是会员的意思呢,一会儿又是DIP,后来又来了个PIP, ...
- 在_vimrc中 set noexpandtab python 不起效果
我ctm,今天配置不让tab转为空格,在_vimrc中set noexpandtab 不起效果. set ts=4也不起效果. 但是在命令行中其效果. 我都不知道咋办了. 问人说我有可能使用的不是那个 ...
- Android Duplicate files copied in APK
今天调试 Android 应用遇到这么个问题: Duplicate files copied in APK META-INF/DEPENDENCIES File 1: httpmime-4.3.2.j ...
- PCA、ZCA白化
白化是一种重要的预处理过程,其目的就是降低输入数据的冗余性,使得经过白化处理的输入数据具有如下性质:(i)特征之间相关性较低:(ii)所有特征具有相同的方差. 白化又分为PCA白化和ZCA白化,在数据 ...
- Fiddler学习笔记
1. [HTTP]Fiddler(一) - Fiddler简介 Fiddler使用代理(127.0.0.1:8888), 打开Fiddler会自动设置该代理. 2.[HTTP]Fiddler(二) - ...