前言

上一篇文章,笔者详细讲述了View三大工作流程的第一个,Measure流程,如果对测量流程还不熟悉的读者可以参考一下上一篇文章。测量流程主要是对View树进行测量,获取每一个View的测量宽高,那么有了测量宽高,就是要进行布局流程了,布局流程相对测量流程来说简单许多。那么我们开始对layout流程进行详细的解析。
ViewGroup的布局流程
上一篇文章提到,三大流程始于ViewRootImpl#performTraversals方法,在该方法内通过调用performMeasure、performLayout、performDraw这三个方法来进行measure、layout、draw流程,那么我们就从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(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); // 1
//省略...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
由上面的代码可以看出,直接调用了①号的host.layout方法,host也就是DecorView,那么对于DecorView来说,调用layout方法,就是对它自身进行布局,注意到传递的参数分别是0,0,host.getMeasuredWidth,host.getMeasuredHeight,它们分别代表了一个View的上下左右四个位置,显然,DecorView的左上位置为0,然后宽高为它的测量宽高。由于View的layout方法是final类型,子类不能重写,因此我们直接看View#layout方法即可:
public void layout(int l, int t, int r, int b) {
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;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); // 1
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b); // 2
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
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);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
首先看①号代码,调用了setFrame方法,并把四个位置信息传递进去,这个方法用于确定View的四个顶点的位置,即初始化mLeft,mRight,mTop,mBottom这四个值,当初始化完毕后,ViewGroup的布局流程也就完成了
那么,我们先看View#setFrame方法:
protected boolean setFrame(int left, int top, int right, int bottom) {
//省略...
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
//省略...
return changed;
}
可以看出,它对mLeft、mTop、mRight、mBottom这四个值进行了初始化,对于每一个View,包括ViewGroup来说,以上四个值保存了Viwe的位置信息,所以这四个值是最终宽高,也即是说,如果要得到View的位置信息,那么就应该在layout方法完成后调用getLeft()、getTop()等方法来取得最终宽高,如果是在此之前调用相应的方法,只能得到0的结果,所以一般我们是在onLayout方法中获取View的宽高信息。
在设置ViewGroup自身的位置完成后,我们看到会接着调用②号方法,即onLayout()方法,该方法在ViewGroup中调用,用于确定子View的位置,即在该方法内部,子View会调用自身的layout方法来进一步完成自身的布局流程。由于不同的布局容器的onMeasure方法均有不同的实现,因此不可能对所有布局方式都说一次,另外上一篇文章是用FrameLayout#onMeasure进行讲解的,那么现在也对FrameLayout#onLayout方法进行讲解:
@Override
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();
//以下四个值会影响到子View的布局参数
//parentLeft由父容器的padding和Foreground决定
final int parentLeft = getPaddingLeftWithForeground();
//parentRight由父容器的width和padding和Foreground决定
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();
//获取子View的测量宽高
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;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//当子View设置了水平方向的layout_gravity属性时,根据不同的属性设置不同的childLeft
//childLeft表示子View的 左上角坐标X值
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
/* 水平居中,由于子View要在水平中间的位置显示,因此,要先计算出以下:
* (parentRight - parentLeft -width)/2 此时得出的是父容器减去子View宽度后的
* 剩余空间的一半,那么再加上parentLeft后,就是子View初始左上角横坐标(此时正好位于中间位置),
* 假如子View还受到margin约束,由于leftMargin使子View右偏而rightMargin使子View左偏,所以最后
* 是 +leftMargin -rightMargin .
*/
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
//水平居右,子View左上角横坐标等于 parentRight 减去子View的测量宽度 减去 margin
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
//如果没设置水平方向的layout_gravity,那么它默认是水平居左
//水平居左,子View的左上角横坐标等于 parentLeft 加上子View的magin值
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
//当子View设置了竖直方向的layout_gravity时,根据不同的属性设置同的childTop
//childTop表示子View的 左上角坐标的Y值
//分析方法同上
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;
}
//对子元素进行布局,左上角坐标为(childLeft,childTop),右下角坐标为(childLeft+width,childTop+height)
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
由源码看出,onLayout方法内部直接调用了layoutChildren方法,而layoutChildren则是具体的实现。
先梳理一下以上逻辑:首先先获取父容器的padding值,然后遍历其每一个子View,根据子View的layout_gravity属性、子View的测量宽高、父容器的padding值、来确定子View的布局参数,然后调用child.layout方法,把布局流程从父容器传递到子元素。
那么,现在就分析完了ViewGroup的布局流程,那么我们接着分析子元素的布局流程。
子View的布局流程
子View的布局流程也很简单,如果子View是一个ViewGroup,那么就会重复以上步骤,如果是一个View,那么会直接调用View#layout方法,根据以上分析,在该方法内部会设置view的四个布局参数,接着调用onLayout方法,我们看看View#onLayout方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
这是一个空实现,主要作用是在我们的自定义View中重写该方法,实现自定义的布局逻辑。
那么到目前为止,View的布局流程就已经全部分析完了。可以看出,布局流程的逻辑相比测量流程来说,简单许多,获取一个View的测量宽高是比较复杂的,而布局流程则是根据已经获得的测量宽高进而确定一个View的四个位置参数。在下一篇文章,将会讲述最后一个流程:绘制流程。希望这篇文章给大家对View的工作流程的理解带来帮助,谢谢阅读。
更多阅读
Android View 测量流程(Measure)完全解析
Android View 绘制流程(Draw) 完全解析
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# View
# 布局
# Android中RecyclerView布局代替GridView实现类似支付宝的界面
# Android自定义View设定到FrameLayout布局中实现多组件显示的方法 分享
# android动态布局之动态加入TextView和ListView的方法
# Android中实现自动生成布局View的初始化代码方法
# Android布局自定义Shap圆形ImageView可以单独设置背景与图片
# Android中ListView Item布局优化技巧
# Android RecyclerView加载不同布局简单实现
# Android ListView添加头布局和脚布局实例详解
# Android自定义ViewGroup之实现FlowLayout流式布局
# 探究Android中ListView复用导致布局错乱的解决方案
# 上一
# 可以看出
# 是一个
# 方法来
# 则是
# 三大
# 自定义
# 重写
# 直接调用
# 先看
# 工作流程
# 这四个
# 的是
# 居左
# 就会
# 是在
# 完成后
# 第一个
# 也就
# 将会
相关文章:
建站168自助建站系统:快速模板定制与SEO优化指南
如何通过多用户协作模板快速搭建高效企业网站?
如何优化Golang Web性能_Golang HTTP服务器性能提升方法
如何配置WinSCP新建站点的密钥验证步骤?
如何用免费手机建站系统零基础打造专业网站?
长沙企业网站制作哪家好,长沙水业集团官方网站?
如何快速生成高效建站系统源代码?
建站之星安装提示数据库无法连接如何解决?
建站之星如何实现五合一智能建站与营销推广?
想学网站制作怎么学,建立一个网站要花费多少?
个人网站制作流程图片大全,个人网站如何注销?
建站主机解析:虚拟主机配置与服务器选择指南
自助网站制作软件,个人如何自助建网站?
如何高效配置IIS服务器搭建网站?
建站主机选购指南:核心配置与性价比推荐解析
西安大型网站制作公司,西安招聘网站最好的是哪个?
网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?
c# 在高并发场景下,委托和接口调用的性能对比
怀化网站制作公司,怀化新生儿上户网上办理流程?
如何快速上传建站程序避免常见错误?
如何用PHP工具快速搭建高效网站?
C++如何使用std::optional?(处理可选值)
历史网站制作软件,华为如何找回被删除的网站?
宿州网站制作公司兴策,安徽省低保查询网站?
江苏网站制作公司有哪些,江苏书法考级官方网站?
如何设置并定期更换建站之星安全管理员密码?
大连 网站制作,大连天途有线官网?
装修招标网站设计制作流程,装修招标流程?
湖州网站制作公司有哪些,浙江中蓝新能源公司官网?
建站VPS选购需注意哪些关键参数?
免费公司网站制作软件,如何申请免费主页空间做自己的网站?
seo网站制作优化,网站SEO优化步骤有哪些?
头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?
网站插件制作软件免费下载,网页视频怎么下到本地插件?
表情包在线制作网站免费,表情包怎么弄?
如何选择高性价比服务器搭建个人网站?
企业网站制作费用多少,企业网站空间一般需要多大,费用是多少?
TestNG的testng.xml配置文件怎么写
如何在企业微信快速生成手机电脑官网?
阿里云网站搭建费用解析:服务器价格与建站成本优化指南
官网自助建站系统:SEO优化+多语言支持,快速搭建专业网站
油猴 教程,油猴搜脚本为什么会网页无法显示?
已有域名能否直接搭建网站?
宁波免费建站如何选择可靠模板与平台?
焦点电影公司作品,电影焦点结局是什么?
如何快速搭建高效简练网站?
建站之星安装路径如何正确选择及配置?
音乐网站服务器如何优化API响应速度?
如何正确下载安装西数主机建站助手?
建站之星图片链接生成指南:自助建站与智能设计教程
*请认真填写需求信息,我们会在24小时内与您取得联系。