上一篇文章我们了解了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工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...
随机推荐
- 如何保存gnome的linux的 会话?相当于windows下的休眠?
在关机前, 你进行的所有操作, 的集合, 就叫做你跟 linux系统 机器间的 一次 会话, 一个session. linux 可以 在关机时保存 这些session, 保存这些打开的窗口 和程序. ...
- 做为一个前端工程师,是往node方面转,还是往HTML5方面转
文章背景:问题本身来自于知乎,但是我感觉这个问题很典型,有必要把问题在整理一下,重新分享出来. 当看到这个问题之前,我也碰到过很多有同样疑惑的同学,他们都有一个共同的疑问该学php还是nodejs,包 ...
- angularjs 新窗口打开
原文链接:angularjs 中state.go 跳转并且打开新的浏览器窗口 业务需要,需要点击打开一个新窗口,并且是点击事件触发的打开新窗口: $scope.lookLook =function(d ...
- 蒙特卡洛模拟入门的几个小例子(R语言实现)
嗯,第一个例子是怎么用蒙特卡洛模拟求pi的值:第二个是用蒙特卡洛模拟求解定积分:第三个是用蒙特卡洛模拟证券市场求解其收益:第四个是用蒙特卡洛模拟验证OLS的参数的无偏性:然后还要R是如何求导,计算导数 ...
- TCP学习之四:传输协议
参考学习张子阳大神的博客:http://www.cnblogs.com/JimmyZhang/category/101698.html 服务端接收到的数据可能是被截断或合并后的数据,需要协议来 ...
- Id.value与document.getElementById("Id").value的区别
如果标签Id在Form表单里面的话,直接Id.value就不能用了,而是要用Form.Id.value来取值或设置值 所以最好用document.getElementById("Id&quo ...
- hdu 4481 Time travel(高斯求期望)(转)
(转)http://blog.csdn.net/u013081425/article/details/39240021 http://acm.hdu.edu.cn/showproblem.php?pi ...
- Yii2 手动安装yii2-imagine插件
由于网络的原因使用composer安装Yii框架,实在太过痛苦,所以这里干脆就手动安装yii-imagine的扩展. 首先下载yii2-image和Imagine扩展库,点击链接就可以从百度云下载上传 ...
- C和指针 第十七章 经典数据类型 堆栈 队列 二叉树
堆栈: // // Created by mao on 16-9-16. // #ifndef UNTITLED_STACK_H #define UNTITLED_STACK_H #define TR ...
- DataTable常用代码
构建DataTable DataTable dtUserInfo = new DataTable("UserInfo"); dtUserInfo.Columns.Add(" ...