0. 前言  

View的绘制流程从ViewRoot的performTraversals开始,经过measure,layout,draw三个流程,之后就可以在屏幕上看到View了。上一篇已经介绍了View和ViewGroup的measure的源码解析过程,本篇介绍measure后如何获得View的宽和高,以及layout和draw的过程。

本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52742887

1.  获取Measure后的宽高

Meaure完成以后,可以通过getMeasuredWidth/Height()来获得View的测量宽高。但是在onMeasure()中拿到的宽高不像在onLayout()中拿到的那么可靠。因为有时View可能需要多次measure,虽然最终测量和最终结果相同,但是前几次measure可能会得到不可靠的宽高。

因为measure的过程和Activity的生命周期没有任何关系,无法确定在哪个生命周期执行完毕以后View的measure过程一定完成。因此在Activity的生命周期里可能无法获得测量宽高。可以尝试如下三种方法获取view的测量宽高。

1.1  焦点相关回调

//重写Activity的onWindowFocusChanged方法
//该方法当Activity得到/失去焦点便会回调,表示View已经初始化完毕,可以保证Measure完成
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = tv.getMeasuredWidth();
int height = tv.getMeasuredHeight();
}
}

1.2 消息队列

//将Runnable置于消息队列尾部,延迟获取测量宽高的时间,保证View初始化完毕
@Override
protected void onStart() {
super.onStart();
view.post(new Runnable() {
@Override
public void run() {
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}


1.3  监听view树

//当View树状态发生改变时回调onGlobalLayout
@Override
protected void onStart() {
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//因为会调用多次,所以拿到测量结果后要remove掉监听
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
});
}

2. Layout过程

ViewRoot的performTraversals()方法会在measure结束后继续执行,并调用View的layout()方法,layout方法是确定本身View的位置,而onLayout方法是确定所有子元素的位置。layout方法如下所示:

//四个参数分别代表相对于父视图左上右下四个坐标值
host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight);
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
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;
if (mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>) 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 &= ~FORCE_LAYOUT;
}

在layout()方法中,第8行首先会调用setFrame()方法来判断视图的大小是否发生过变化,避免未发生变化时进行重绘操作。

13行会调用onLayout()方法,用于父容器确定子元素的位置,这是一个空实现,View和ViewGrope均没有实现它。

我们尝试自定义一个布局,借此来理解onLayout()的过程。代码如下所示:

public class MyLayout extends ViewGroup {
public MyLayout (Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//调用measureChild()来测量出子元素的大小
measureChild(getChildAt(0), widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//子元素可以在xml中任意添加一个控件
//根据测量结果布置子元素在MyLayout中的位置
childView.layout(0, 0, getChildAt(0).getMeasuredWidth(),getChildAt(0).getMeasuredHeight());
}
}

onLayout()过程结束后,我们就可以调用getWidth/Height来获取View的最终宽高了。之前也提到过, getMeasureWidth方法在measure过程结束后取到的是测量宽高,两者几乎在任何情况下都是相同的。但是也有非一般的情况,原因就是我们可以重写onLayout,并且对measure测量的结果"置之不理"。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//这样getWidth得到的值就是100减0,虽然这么做毫无意义
getChildAt(0).layout(0, 0, 100, 100);
}

3. Draw过程

measure和layout的过程都结束后,就进入到对View进行绘制的draw过程了。

ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作。代码如下所示:

public void draw(Canvas canvas) {
//…
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;
// 第一步,对背景进行绘制
int saveCount;
if (!dirtyOpaque) {
//得到android:background属性设置的图片或颜色
final Drawable background = mBGDrawable;
if (background != null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
//根据layout的View位置结果确定背景绘制区域
background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
//调用Drawable的draw()方法来完成背景的绘制工作
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
} final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
//第三步,对内容进行绘制,onDraw为空实现,需要子类实现
if (!dirtyOpaque) onDraw(canvas);
//第四步,ViewGroup的dispatchDraw()方法遍历调用所有子元素的draw()
dispatchDraw(canvas);
//第六步,显示控件的滚动条
onDrawScrollBars(canvas);
// we're done...
return;
}
}

从源码中可以看出,onDraw()是需要我们去重写实现的。

绘制主要是借助Canvas这个类,它会作为参数传入到onDraw()方法中,基本可以把它当成一块画布,在上面绘制任意的东西。

4. setWillNotDraw方法

/**
* If this view doesn't do any drawing on its own, set this flag to allow further optimizations.
* By default, this flag is not set on View, but could be set on some View subclasses such as ViewGroup.
*Typically, if you override onDraw(Canvas),you should clear this flag.
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}

如果你的自定义View不需要绘制出来的话,就可以设置这个方法为true,这样可以优化执行速度。

ViewGrope默认设置为true,因为ViewGrope多数都是只负责布局。如果我们继承自ViewGrope的自定义控件需要通过onDraw绘制内容时,需要手动关闭这个标记位。

public class MyLayout extends LinearLayout {
public MyLayout (Context context, intposition) {
super(context);
setWillNotDraw(false);
} @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
}

至此关于View绘制的过程便总结完毕。

转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52742887

Android开发——View绘制过程源码解析(二)的更多相关文章

  1. Android开发——View绘制过程源码解析(一)

    )UNSPECIFIED:表示View可以设置成任意的大小,没有任何限制.这种情况比较少见. 2. MeasureSpec的生成过程 2.1 顶级View的MeasureSpec // desired ...

  2. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  3. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  4. Android之View绘制流程源码分析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于稍有自定义View经验的安卓开发者来说,onMeasure,onLayout,onDraw这三个方法都不会陌生,起码多少都有所接触吧. 在安卓中 ...

  5. 自定义控件(View的绘制流程源码解析)

    参考声明:这里的一些流程图援引自http://a.codekk.com/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7% ...

  6. 基于Android开发的天气预报app(源码下载)

    原文:基于Android开发的天气预报app(源码下载) 基于AndroidStudio环境开发的天气app -系统总体介绍:本天气app使用AndroidStudio这个IDE工具在Windows1 ...

  7. android 开发 View _1_ View的子类们 和 视图坐标系图

    目录: android 开发 View _2_ View的属性动画ObjectAnimator ,动画效果一览 android 开发 View _3_ View的属性动画ValueAnimator a ...

  8. Android IntentService使用介绍以及源码解析

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.IntentService概述及使用举例 IntentService内部实现机制用到了HandlerThread,如果对HandlerThrea ...

  9. Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段

    目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...

随机推荐

  1. 函数名: lseek

    函数名: lseek 功 能: 移动文件读/写指针 头文件:#include <sys/types.h> #include <unistd.h> 用 法: off_t lsee ...

  2. 解决SQLite打开已有路径下的db问题

    最近遇到的需要加载已有路径下(sd card下)db的问题,找了一下资料,以下是解决的方法,仅供参考(转载自eoe): SQLiteOpenHelper 是Android框架为我们提供的一个非常好的数 ...

  3. [原]Android打包之Eclipse打包

    Android自动打包流程详细图: 步骤一: 在工程中新建一个build.xml. 步骤二: 给工程配置ant工具. 选择ant工具的步骤如下: Windows->Shown view-> ...

  4. 数据库-identifying 与non-identifying realtionship 区别

    MySQL Workbench 或者是 E-RWin等进行数据库建模时,通常会对数据表进行关联操作,即设置表与表之间的关系 1:1 1:n m:n,而它们具有 identifying realtion ...

  5. 【[TJOI2007]可爱的质数】

    题目 用一道板子题来复习一下\(bsgs\) \(bsgs\)用于求解形如 \[a^x\equiv b(mod\ p)\] 这样的高次不定方程 由于费马小定理的存在,我们可是直接暴力扫一遍\(p\), ...

  6. Educational Codeforces Round 53 (Rated for Div. 2) C. Vasya and Robot 【二分 + 尺取】

    任意门:http://codeforces.com/contest/1073/problem/C C. Vasya and Robot time limit per test 1 second mem ...

  7. Windows 备用数据流(ADS)的妙用___转载

    NTFS交换数据流(Alternate Data Streams,简称ADS)是NTFS磁盘格式的一个特性,在NTFS文件系统下,每个文件都可以存在多个数据流.通俗的理解,就是其它文件可以“寄宿”在某 ...

  8. 【翻译】苹果官网的命名规范之 Naming Properties and Data Types

    苹果官方原文:Naming Properties and Data Types 前言:纯属练习英语和学习.翻译错误和不通顺的地方敬请谅解和指正.O(∩_∩)O 属性和数据类型的命名 本节讲述了属性定义 ...

  9. 【洛谷P2831】[NOIP2016]愤怒的小鸟

    愤怒的小鸟 题目链接 本来是刷状压DP的,然而不会.. 搜索是比较好想的,直接dfs就行了 我们可以知道两只猪确定一条抛物线 依次处理每一只猪,有以下几种方法: 1.先看已经建立的抛物线是否能打到这只 ...

  10. 学大伟业 Day 6 培训总结

    今天接着昨天的继续讲数据结构 今天先是 分块 在统计问题中,尤其是序列问题,经常涉及到区间的操作,比如修改一段区间的元素,询问某个区间的元素的信息. 如果每次都对一整个区间的每一个元素进行操作的话,那 ...