Android View体系(八)从源代码解析View的layout和draw流程

时间:2022-09-19 21:56:14

相关文章

Android View体系(一)视图坐标系

Android View体系(二)实现View滑动的六种方法

Android View体系(三)属性动画

Android View体系(四)从源代码解析Scroller

Android View体系(五)从源代码解析View的事件分发机制

Android View体系(六)从源代码解析Activity的构成

Android View体系(七)从源代码解析View的measure流程

前言

上一篇文章我们讲了View的measure的流程。接下来我们讲下View的layout和draw流程,假设你理解了View的measure的流程。那这篇文章自然就不在话下了。

1.View的layout流程

先来看看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); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
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;
}

传进来里面的四个參数各自是View的四个点的坐标。它的坐标不是相对屏幕的原点,并且相对于它的父布局来说的。

l 和 t 是子控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离。

r 和 b是子控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。来看看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) {
changed = true; // Remember our drawn bit
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 our old position
invalidate(sizeChanged); mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); ...省略
}
return changed;
}

在setFrame()方法里主要是用来设置View的四个顶点的值,也就是mLeft 、mTop、mRight和 mBottom的值。在调用setFrame()方法后,调用onLayout()方法:

  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

onLayout()方法没有去做什么,这个和onMeasure()方法相似,确定位置时依据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout()方法。

既然这样,我们就来看看LinearLayout的onLayout()方法:

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

layoutVertical做了什么呢?

 void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft; int childTop;
int childLeft; // Where right end of child should go
final int width = right - left;
int childRight = width - mPaddingRight; // Space available for child
int childSpace = width - paddingLeft - mPaddingRight; final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; switch (majorGravity) {
case Gravity.BOTTOM:
// mTotalLength contains the padding already
childTop = mPaddingTop + bottom - top - mTotalLength;
break; // mTotalLength contains the padding already
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break; case Gravity.TOP:
default:
childTop = mPaddingTop;
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(); final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break; case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break; case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
} if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
} childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i);
}
}
}

这种方法会遍历子元素并调用setChildFrame()方法:

  private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}

在setChildFrame()方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值是逐渐增大的,这是为了在垂直方向,子元素是一个接一个排列的而不是重叠的。

2.View的draw流程

View的draw流程非常easy。先来看看View的draw()方法:

 public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; // Step 1, draw the background, if needed
int saveCount; if (!dirtyOpaque) {
drawBackground(canvas);
}
...省略
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft; final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
...省略
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children
dispatchDraw(canvas);
...省略
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
...省略
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
}

从源代码的凝视我们看到draw流程有六个步骤。当中第2步和第5步能够跳过:

  1. 假设有设置背景。则绘制背景
  2. 保存canvas层
  3. 绘制自身内容
  4. 假设有子元素则绘制子元素
  5. 绘制效果
  6. 绘制装饰品(scrollbars)

好了,关于View的工作流程就说到这里了,接下来会讲到自己定义View。

Android View体系(八)从源代码解析View的layout和draw流程的更多相关文章

  1. Android View体系(八)从源码解析View的layout和draw流程

    前言 上一篇文章我们讲了View的measure的流程,接下来我们讲下View的layout和draw流程,如果你理解了View的measure的流程,那这篇文章自然就不在话下了. 1.View的la ...

  2. Android View体系(九)自定义View

    相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...

  3. Android View 事件分发机制 源代码解析 (上)

    一直想写事件分发机制的文章,无论咋样,也得自己研究下事件分发的源代码.写出心得~ 首先我们先写个简单的样例来測试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个M ...

  4. 源码分析篇 - Android绘制流程(二)measure、layout、draw流程

    performTraversals方法会经过measure.layout和draw三个流程才能将一帧View需要显示的内容绘制到屏幕上,用最简化的方式看ViewRootImpl.performTrav ...

  5. Android View体系(二)实现View滑动的六种方法

    1.View的滑动简介 View的滑动是Android实现自定义控件的基础,同时在开发中我们也难免会遇到View的滑动的处理.其实不管是那种滑动的方式基本思想都是类似的:当触摸事件传到View时,系统 ...

  6. Android View体系(四)从源码解析Scroller

    在Android View体系(二)实现View滑动的六种方法这篇文章中我们讲到了用Scroller来实现View的滑动,所以这篇文章我们就不介绍Scroller是如何使用的了,本篇就从源码来分析下S ...

  7. Android View体系(十)自定义组合控件

    相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...

  8. Android View 的绘制流程之 Layout 和 Draw 过程详解 &lpar;二&rpar;

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

  9. Android View框架总结(三)View工作原理

    转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52180375 测量/布局/绘制顺序 如何引起View的测量/布局/绘制? Perfor ...

随机推荐

  1. 【STL源码学习】STL算法学习之四

    排序算法是STL算法中相当常用的一个类别,包括部分排序和全部排序算法,依据效率和应用场景进行选择. 明细: sort 函数原型: template <class RandomAccessIter ...

  2. C&plus;&plus;中的函数指针用法

    代码: #include <iostream> #include <cstdio> typedef void (*FUN)(); using namespace std; vo ...

  3. enum可以做索引

    enum可以做索引 enum可以做索引, 配上虚函数,或者函数指针,可以实现上层的统一封装和快速索引. 点击(此处)折叠或打开 MoTbl.cpp #include <stdio.h> # ...

  4. 制作H5像一个div中一张长图,里边是一条一条信息,需要点击的响应式方法

    <style> .nav_box { margin-top: 20vh } .section1 .directory { margin-top: 4vh; position: relati ...

  5. 使用aliyun cli工具快速创建云主机

    参考文档: https://help.aliyun.com/document_detail/25484.html?spm=a2c4g.11186623.3.2.b57vQp 步骤 创建AccessID ...

  6. Redis 教程 Java工程师学习知识点

    1. Redis简介及安装 1.1 Redis 简介 Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库. Redis 与其他 key - value 缓存产品有以下 ...

  7. JS的DOM操作 - 你真的了解吗&quest;

    摘要 想稍微系统的说说对于DOM的操作,把Javascript和jQuery常用操作DOM的内容归纳成思维导图方便阅读,同时加入性能上的一些问题. 前言 在前端开发的过程中,javascript极为重 ...

  8. ZH奶酪:Python中range和xrange的区别

    range    函数说明:range([start,] stop[, step]),根据start与stop指定的范围以及step设定的步长,生成一个序列.range示例: >>> ...

  9. 首部讲Python爬虫电子书 Web Scraping with Python

    首部python爬虫的电子书2015.6pdf<web scraping with python> http://pan.baidu.com/s/1jGL625g 可直接下载 waterm ...

  10. 关于zepto 选择特定值的input 报错问题

    zepto 选择特定值的input  时,需要用单引号或双引号引用这个特定值 否则 报错