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

时间:2022-12-31 22:43:53

到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程。只是在学习layout过程之前。大家有没有发现我换了编辑器,哈哈。最终下定决心从Html编辑器切换为markdown编辑器。这里之所以使用“下定决心”这个词。是由于毕竟Html编辑器使用好几年了。非常多习惯都已经养成了,要改变多年的习惯确实不易。相信这也是还有非常多人坚持使用Html编辑器的原因。

这也反应了一个现象。当人对某一事物非常熟悉时,一旦出现了新的事物想代替老的事物时,人们都有一种抵触的情绪,做技术的人也一样,当他对某一技术非常熟悉时,当新的同类技术出来时,都有抵触情绪,这也就是网上总有人讨论各种编程语言的好坏的原因,同一时候你会发现一个问题。当你对某种技术非常熟悉时。假设完毕某项任务使用你熟悉的技术完毕工作量非常大,而使用第二种新的技术却非常easy实现,相信大部分都会选择熟悉的技术实现,即使他的工作量非常大。正如html编辑器和markdown编辑器一样。markdown在排版方面明显比html编辑器强大,可是还有非常多人不愿意切换过来。可是我今天想说的事实上是作为程序猿,我们的领域假设出现了新的技术,在我们有精力的前提下我们还是应该花时间去研究,至少不应该有种抵触的情绪。好吧,今天扯淡就到这里吧….

如今就開始学习ViewGroup的layout过程吧,假设你还没有学习过我前面的文章。建议先去阅读前面两篇相关文章

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

Android 中View的绘制机制源代码分析二

如同measure方法一样,layout方法也是从ViewRoot类的performTraversals方法调用,代码例如以下:

 final boolean didLayout = mLayoutRequested;
boolean triggerGlobalLayoutListener = didLayout
|| attachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
mLayoutRequested = false;
mScrollMayChange = true;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(
"ViewRoot", "Laying out " + host + " to (" +
host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")");
long startTime = 0L;
if (Config.DEBUG && ViewDebug.profileLayout) {
startTime = SystemClock.elapsedRealtime();
}
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) {
if (!host.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_LAYOUT)) {
throw new IllegalStateException("The view hierarchy is an inconsistent state,"
+ "please refer to the logs with the tag "
+ ViewDebug.CONSISTENCY_LOG_TAG + " for more infomation.");
}
}

我们发现调用的就是host.layout(0,0,host.mMeasureWidth,host.mMeasureHeight),在前面的文章中已经说过host就是DecorView。host.mMeasuredWidth和host.mMeasureHeight经过了measure过程后分别就是host的宽度和高度,事实上也就是屏幕的宽度和高度。layout方法是View中的一个方法。我们先看看layout的代码吧

    /**
*
* @param l Left position, relative to parent
* @param t Top position, relative to parent
* @param r Right position, relative to parent
* @param b Bottom position, relative to parent
*/
public final void layout(int l, int t, int r, int b) {
boolean changed = setFrame(l, t, r, b);
if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
}
mPrivateFlags &= ~FORCE_LAYOUT;
}

layout和measure一样,是一个final方法,所以子类无法改变它的行为,在layout中主要调用onLayout方法完毕实际的逻辑,可是并非每次laout方法都会调用onLayout方法的,首先会调用setFrame方法将上下左右的位置分别保存起来,而且在setFrame方法中会推断和上次的上下左右的位置是否一样,假设不一样保存起来并返回true,否则直接返还false.仅仅有返还true或者有LAYOUT_REQUIRED标记才会调用onLayout方法,而onLayout方法须要子类(ViewGroup)自己去依据自己的情况实现,所以在自己定义ViewGroup时,常常须要改写onLayout。在onLayout里面我们能够依据自己的需求在布局View在ViewGroup的摆放位置。至于layout的四个參数凝视里面已经写清楚了。分别代表View 左边,顶部,右边,底部在父视图中的位置,通过上面传入的參数,能够知道host在屏幕中是满屏的。为了对layout有更深入的理解,我这里使用LinearLayout解说怎样利用layout进行子View的位置分配。

    @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical();
} else {
layoutHorizontal();
}
}

在LinearLayout的onLayout方法中,和onMeasure方法一样,依据当前LinearLayout的排列方式分别调用layoutVertical和LayoutHorizontal,这里我们还是看看竖排的 layoutVertical吧

    /**
* Position the children during a layout pass if the orientation of this
* LinearLayout is set to {@link #VERTICAL}.
*
* @see #getOrientation()
* @see #setOrientation(int)
* @see #onLayout(boolean, int, int, int, int)
*/
void layoutVertical() {
//距离左边的距离
final int paddingLeft = mPaddingLeft;
//child的顶部,默认情况等于顶部pading
int childTop = mPaddingTop;
int childLeft; // LinearLayout可用宽度
final int width = mRight - mLeft;
int childRight = width - mPaddingRight; // Space available for child
int childSpace = width - paddingLeft - mPaddingRight;
//子View的个数
final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
//依据LinearLayotu的gravity的值计算childTop的位置
if (majorGravity != Gravity.TOP) {
switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already, we add the top
// padding to compensate
childTop = mBottom - mTop + mPaddingTop - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
childTop += ((mBottom - mTop) - mTotalLength) / 2;
break;
} } for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//拿到子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
//计算子View在水平方向的childLeft
switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.LEFT:
childLeft = paddingLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
default:
childLeft = paddingLeft;
break;
} childTop += lp.topMargin; setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}

事实上LinearyLayout的layoutVertical方法的逻辑非常easy:首先计算子View在LinearLayout中的起始位置。也就是上面的childTop,就算时首先推断当前LinearLayout在垂直方向上的 对齐方式:

1. 假设是Gravity.Bottom,那么childTop = mBottom - mTop + mPaddingTop - mTotalLength; 这个非常好理解,所以假设mTotalLenght比屏幕的高度大时,childTop非常有可能是负值。从而顶部看不见

2. 假设是Gravity.CENTER_VERTICAL。那么childTop += ((mBottom - mTop) - mTotalLength) / 2;

3.假设是Gravity.Top 那么childTop = mPaddingTop; 这样的是默认值 三种对齐方式相应的效果图例如以下:

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

childTop计算完毕后開始遍历各个子View,依据LinearLayout的水平方向的布局计算childLeft,这里可能有些人就犯糊涂了。这里是垂直布局,为什么要看水平方向,由于即使垂直方向,子视图也能够水平居中,所以不同的水平布局算childLeft是不一样的。

假设是Gravity.LEFT 那么childLeft = paddingLeft + lp.leftMargin;

假设是Gravity.CENTER_HORIZONTAL 那么childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin;

假设是Gravity.RIGHT 那么是
childLeft = childRight - childWidth - lp.rightMargin;`

如今childTop和childLeft都计算好了,由于已经measure过。所以childBottom和childRight非常easy算出,这里调用了setChildFrame方法,该方法实际就是调用child.layout方法设置child的布局位置。

至此,LinearLayout的布局过程已经解说完毕。

Android 中View的绘制机制源代码分析 三的更多相关文章

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

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

  2. Android 中View的绘制机制源代码分析 二

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要解说 ...

  3. Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 onMeasure方法简述 附有自定义View例子 Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android fr ...

  4. 【转】Android中View的绘制过程 onMeasure方法简述 附有自定义View例子

    Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布 ...

  5. android 中view的绘制过程

    view的绘制过程中分别会执行:onMeasure(会多次)计算view的大小,OnLayout(),确定控件的大小和位置 onDraw()绘制view 当Activity获得焦点时,它将被要求绘制自 ...

  6. Android中View的绘制流程(专题讲解)

    Android中的UI视图有两种方式实现:.xml文件(实现代码和UI的分离)和代码实现. Android的UI框架基本概念: 1. Activity:基本的页面单元,Activity包含一个Wind ...

  7. Android中View绘制流程以及invalidate&lpar;&rpar;等相关方法分析

    [原文]http://blog.csdn.net/qinjuning 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简 ...

  8. Android中View绘制流程以及invalidate&lpar;&rpar;等相关方法分析(转载的文章,出处在正文已表明)

    转载请注明出处:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时 ...

  9. Android中View绘制流程以及invalidate&lpar;&rpar;等相关方法分析(转)

    转自:http://blog.csdn.net/qinjuning 前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴 ...

随机推荐

  1. PHP fastcgi模式大文件上传500错误

    最近在项目中中上传图片时,大约有300多K,结果报了个服务器错误,以前从未遇到过,错误的内容如下: mod_fcgid: www.111cn.net HTTP request length 13229 ...

  2. shell脚本 gawk语言 综采话单 对账 字段核对

    今天被分配到对账组下的字段程序的了解和修改,在 SecureCRT 下的 run.sh 是字段对账的主程序,其中run.sh文件中含有gawk的代码. 程序的主要功能为 有两个文件夹 source存的 ...

  3. 服务器数据库挂掉 Can &&num;39&semi;t connect to local MySQL server through socket &&num;39&semi;&sol;var&sol;lib&sol;mysql&sol;mysql&period;sock &&num;39&semi;&lpar;2&rpar; &quot&semi;&semi;

    刚刚遇到这个报错,我无语了,我这么个菜逼,咋解决,还好师兄(付付)解决了,付付真棒,在此记录一下,以供学习 Can 't connect to local MySQL server through s ...

  4. C&num; 中&quest;和&quest;&quest;的用法

    最近在看官方的源码时,经常看到有 Int? sum; 和 FileProvider = FileProvider ??builder.GetFileProvider(); 一个问号: 很多数据类型时不 ...

  5. The Non-Inverting Amplifier Output Resistance by Adrian S&period; Nastase &lbrack;转载&rsqb;

    Source Address: http://masteringelectronicsdesign.com/the-non-inverting-amplifier-output-resistance/ ...

  6. SQL Server重置INDETITY的开始值

    @@IDENTITY 和SCOPE_IDENTITY 返回在当前会话中的任何表内所生成的最后一个标识值.但是,SCOPE_IDENTITY 只返回插入到当前作用域中的值:@@IDENTITY 不受限于 ...

  7. jQuery对标签、类样式、值、文档、DOM对象的操作

    jquery的标签属性操作 使用attr()方法对html标签属性进行操作,attr如果参数是一个参数,表示获取html标签的属性值,如果是两个参数则是设置标签属性名以及对象的属性值 .prop()适 ...

  8. Python datetime&period;md

    datetime datetime模块包含了一些用于时间解析.格式化.计算的函数. Times 时间值由time类来表示, Times有小时, 分, 秒和微秒属性. 以及包含时区信息. 初始化time ...

  9. Quartus II 安装教程—FPGA入门教程【钛白Logic】

    Quartus II 工具安装一般分为两个部分,首先是开发工具本身的安装,其次就是器件库的安装,我们可以根据我们的需要选择相应的器件库来安装,这里我们使用Cyclone IV的FPGA,即安装Cycl ...

  10. AVFoundation-视频录制以及拍照

    一般如果UI和UE在设计时只要求功能,对相机界面没什么要求的话,个人觉得调用系统相机(UIImagePickerController)就可以满足我们的需求比如照相或者录制视频,但是考虑界面美观性,有时 ...