Android中的Veiw从内存中到呈如今UI界面上须要依次经历三个阶段:量算 -> 布局 -> 画图,关于View的量算、布局、画图的整体机制可參见博文 《 Android中View的布局及画图机制》

量算是布局的基础。假设想了解量算的细节,可參见博文《源代码解析Android中View的measure量算过程》。本文将从源代码角度解析View的布局layout过程。本文会具体介绍View布局过程中的关键方法,并对源代码加上了凝视以进行说明。

对View进行布局的目的是计算出View的尺寸以及在其父控件中的位置,具体来说就是计算出View的四条边界分别到其父控件左边界、上边界的距离,即计算View的left、top、right、bottom的值。


layout

layout()方法是View布局的入口,其源代码例如以下所看到的:

    public void layout(int l, int t, int r, int b) {
//成员变量mPrivateFlags3中的一些比特位存储着和layout相关的信息
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
//假设在mPrivateFlags3的低位字节的第4位(从最右向左数第4位)的值为1,
//那么就表示在layout布局前须要先对View进行量算。
//这样的情况下就会运行View的onMeasure方法对View进行量算
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
//量算完毕后就会将mPrivateFlags3低位字节的第4位重置为0,
//移除掉标签PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight; //假设isLayoutModeOptical()返回true,那么就会运行setOpticalFrame()方法。
//否则会运行setFrame()方法。而且setOpticalFrame()内部会调用setFrame(),
//所以不管怎样都会运行setFrame()方法。 //setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中
//而且返回一个boolean值,假设返回true表示View的位置或尺寸发生了变化。
//否则表示未发生变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//假设View的布局发生了变化,或者mPrivateFlags有须要LAYOUT的标签PFLAG_LAYOUT_REQUIRED,
//那么就会运行以下代码
//首先会触发onLayout方法的运行,View中默认的onLayout方法是个空方法
//只是继承自ViewGroup的类都须要实现onLayout方法,从而在onLayout方法中依次循环子View,
//并调用子View的layout方法
onLayout(changed, l, t, r, b);
//在运行完onLayout方法之后,从mPrivateFlags中移除标签PFLAG_LAYOUT_REQUIRED
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; //我们能够通过View的addOnLayoutChangeListener(View.OnLayoutChangeListener listener)方法
//向View中增加多个Layout发生变化的事件监听器
//这些事件监听器都存储在mListenerInfo.mOnLayoutChangeListeners这个ArrayList中
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
//首先对mOnLayoutChangeListeners中的事件监听器进行拷贝
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
//遍历注冊的事件监听器,依次调用其onLayoutChange方法,这样Layout事件监听器就得到了响应
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
} //从mPrivateFlags中移除强制Layout的标签PFLAG_FORCE_LAYOUT
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
//向mPrivateFlags3中增加Layout完毕的标签PFLAG3_IS_LAID_OUT
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
  • 在layout()方法内部刚開始运行的时候,首先会依据mPrivateFlags3变量是否具有标志位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT推断是否须要运行View的onMeasure()方法。

    假设具有标志位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT,则运行onMeasure()方法,从而对View进行量算,量算的结果会保存到View的成员变量中。量算完毕后就会将mPrivateFlags3低位字节的第4位重置为0,移除掉标签PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT。

  • 假设isLayoutModeOptical()返回true。那么就会运行setOpticalFrame()方法,否则会运行setFrame()方法。而且setOpticalFrame()内部会调用setFrame(),所以不管怎样都会运行setFrame()方法。setFrame()方法会将View新的left、top、right、bottom存储到View的成员变量中,而且返回一个boolean值,假设返回true表示View的位置或尺寸发生了变化。否则表示未发生变化。后面会对setFrame()方法具体介绍。

  • 假设View的布局发生了变化,或者mPrivateFlags有须要LAYOUT的标签PFLAG_LAYOUT_REQUIRED,就会触发onLayout方法的运行,View中默认的onLayout方法是个空方法。

    只是继承自ViewGroup的类都须要实现onLayout方法。从而在onLayout方法中依次循环子View,并调用子View的layout方法。

    在运行完onLayout方法之后,从mPrivateFlags中移除标签PFLAG_LAYOUT_REQUIRED。然后会遍历注冊的Layout Change事件监听器。依次调用其onLayoutChange方法,这样Layout事件监听器就得到了响应。

  • 最后,从mPrivateFlags中移除强制Layout的标签PFLAG_FORCE_LAYOUT,向mPrivateFlags3中增加Layout完毕的标签PFLAG3_IS_LAID_OUT。


setFrame

setFrame()方法是具体用来完毕给View分配尺寸以及位置工作的。在layout()方法中会调用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 + ")");
} if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
//将新旧left、right、top、bottom进行对照。仅仅要不全然相对就说明View的布局发生了变化,
//则将changed变量设置为true
changed = true; //先保存一下mPrivateFlags中的PFLAG_DRAWN标签信息
int drawn = mPrivateFlags & PFLAG_DRAWN; //分别计算View的新旧尺寸
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
//比較View的新旧尺寸是否同样,假设尺寸发生了变化,那么sizeChanged的值为true
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position
invalidate(sizeChanged); //将新的left、top、right、bottom存储到View的成员变量中
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
//mRenderNode.setLeftTopRightBottom()方法会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,
//该方法会依据left、top、right、bottom更新用于渲染的显示列表
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); //向mPrivateFlags中增加标签PFLAG_HAS_BOUNDS,表示当前View具有了明白的边界范围
mPrivateFlags |= PFLAG_HAS_BOUNDS; if (sizeChanged) {
//假设View的尺寸和之前相比发生了变化,那么就运行sizeChange()方法。
//该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
} if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
//有可能在调用setFrame方法之前,invalidate方法就被调用了,
//这会导致mPrivateFlags移除了PFLAG_DRAWN标签。
//假设当前View处于可见状态就将mPrivateFlags强制增加PFLAG_DRAWN状态位,
//这样会确保以下的invalidate()方法会运行到其父控件级别。
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
//invalidateParentCaches()方法会移除其父控件的PFLAG_INVALIDATED标签。
//这样其父控件就会重建用于渲染的显示列表
invalidateParentCaches();
} // 又一次恢复mPrivateFlags中原有的PFLAG_DRAWN标签信息
mPrivateFlags |= drawn; mBackgroundSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
} notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
  • 在该方法中,会将新旧left、right、top、bottom进行对照,仅仅要不全然同样就说明View的布局发生了变化,则将changed变量设置为true。

    然后比較View的新旧尺寸是否同样。假设尺寸发生了变化。并将其保存到变量sizeChanged中。假设尺寸发生了变化,那么sizeChanged的值为true。

  • 然后将新的left、top、right、bottom存储到View的成员变量中保存下来。并运行mRenderNode.setLeftTopRightBottom()方法会。其会调用RenderNode中原生方法的nSetLeftTopRightBottom()方法,该方法会依据left、top、right、bottom更新用于渲染的显示列表。

  • 假设View的尺寸和之前相比发生了变化。那么就运行sizeChange()方法,该方法中又会调用onSizeChanged()方法,并将View的新旧尺寸传递进去。

  • 假设View处于可见状态,那么会调用invalidate和invalidateParentCaches方法。invalidateParentCaches()方法会移除其父控件的PFLAG_INVALIDATED标签,这样其父控件就会重建用于渲染的显示列表。


sizeChange

sizeChange方法会在View的尺寸发生变化时调用,在setFrame()方法中就可能会调用sizeChange()方法。

当然。在View的setLeft()、setTop()、setRight()、setBottom()等其它改变View尺寸的方法中也会调用sizeChange()方法,其源代码例如以下所看到的:

    private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
//将View的新旧尺寸传递给onSizeChanged()方法
onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
if (mOverlay != null) {
mOverlay.getOverlayView().setRight(newWidth);
mOverlay.getOverlayView().setBottom(newHeight);
}
rebuildOutline();
}

在该方法中其主要将View的新旧尺寸传递给onSizeChanged()方法使其运行。


onSizeChanged

onSizeChanged()方法是个空方法,代码例如以下所看到的:

    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
}

该方法会在View的尺寸发生变化时,通过sizeChange()方法的运行而被调用。当View第一次增加到View树中时,该方法也会被调用。仅仅只是传入的旧尺寸oldWidth和oldHeight都是0。


总结

layout方法总的调用过程主线例如以下所看到的:

layout() -> onMeasure() -> setFrame() -> sizeChange() -> onSizeChanged() -> onLayout() ->遍历运行OnLayoutChangeListener.onLayoutChange()

希望本文对大家理解View的layout布局过程有所帮助!

相关阅读:

《我的Android博文整理汇总》

《 Android中View的布局及画图机制》

《源代码解析Android中View的measure量算过程》

源代码解析Android中View的layout布局过程的更多相关文章

  1. 源码解析Android中View的measure量算过程

    Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算.布局.绘图的总体机制可参见博文< Android中View ...

  2. Android中View的layout mechanism(布局机制)

    layout mechanism Android中View的layout mechanism主要分为两个阶段:measure阶段和layout阶段.layout mechanism按照一定的顺序进行, ...

  3. 使用具体解释及源代码解析Android中的Adapter、BaseAdapter、ArrayAdapter、SimpleAdapter和SimpleCursorAdapter

    Adapter相当于一个数据源,能够给AdapterView提供数据.并依据数据创建相应的UI.能够通过调用AdapterView的setAdapter方法使得AdapterView将Adapter作 ...

  4. android中监听layout布局

    android开发可以对layout文件夹中的xml文件里的布局进行监听,并处理事件,如:对RelativeLayout,LinearLayout,FrameLayout,GridLayout等布局容 ...

  5. 深入源代码解析Android中的Handler,Message,MessageQueue,Looper

    本文主要是对Handler和消息循环的实现原理进行源代码分析.假设不熟悉Handler能够參见博文< Android中Handler的使用>,里面对Android为何以引入Handler机 ...

  6. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  7. Android 自定义View及其在布局文件中的使用示例(三):结合Android 4.4.2_r1源码分析onMeasure过程

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3549365.html From crash_coder linguowu linguowu0622@gami ...

  8. Android 自定义View及其在布局文件中的使用示例(二)

    转载请注明出处 http://www.cnblogs.com/crashmaker/p/3530213.html From crash_coder linguowu linguowu0622@gami ...

  9. Android 中View的绘制机制源代码分析 一

    尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差点儿相同半年没有写博客了,一是由于工作比較忙,二是认为没有什么内容值得写, ...

随机推荐

  1. 【转】PHP笔试题2010年

    From : http://www.51projob.com/a/PHP/20120905/602.html 下午,还有一场比较大的面试等着我[虽然接到pps的录用电话,可是心里还是想去verycd试 ...

  2. JVM编译优化

    在部分的商用虚拟机中,Java 程序最初是通过解释器(Interpreter )进行解释执行的,当虚拟机发现某个方法或代码块的运行特别频繁的时候,就会把这些代码认定为“热点代码”.为了提高热点代码的执 ...

  3. C++ 反射机制的简单实现

    C++并不支持反射机制,只能自己实现. 如果需要实现字字符串到函数到映射,一定要使用到函数指针. 简单实现反射机制,根据字符串来构造相应到类.主要有以下几点: (1) 可以使用map保存字符从到函数指 ...

  4. jquery 控制css样式

    一.CSS 1.css(name) 访问第一个匹配元素的样式属性. 返回值 String 参数 name (String) : 要访问的属性名称 示例: $("p").css(&q ...

  5. C# 播放铃声最简短的代码实现方式

    因为只是做一个软件的闹铃播放效果,到网上找的时候试了几种,哎,都失败了,而且代码挺杂的,最终一句搞定了: 1         // 窗体加载事件 2         private void Time ...

  6. centos7 tomcat9

    1.下载 下载 apache-tomcat-9.0.0.M4.tar.gz 文件:  wget http://mirror.bit.edu.cn/apache/tomcat/tomcat-9/v9.0 ...

  7. Linux中挂载新的磁盘到指定目录或分区

    新增磁盘的设备文件名为 /dev/vdb 大小为100GB. #fdisk -l  查看新增的的磁盘 1.对新增磁盘进行分区 #fdisk /dev/vdb 按提示操作 p打印  n新增 d 删除 w ...

  8. KDiff

    BeyondCompare是收费的,用了一段时间不能用了.找到一个 KDiff做对比工具,也很好用. 在这里下载: http://sourceforge.net/projects/kdiff3/fil ...

  9. oauth2-server-php-docs 集成3

    Yii集成 对于Yii集成,请通过Filsh查看Yii OAuth2服务器资源库 CakePHP的 有关CakePHP中集成的这个库的示例,请参阅Qsoomro CakePHP OAuth2 Demo ...

  10. [Algorithm] Powerset Problem

    By given an array [1,2,3]: Return all possible combinations. for example: [[], [1], [2], [3], [1, 2] ...