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. March 16 2017 Week 11 Thursday

    Adventure may hurt you, but monotony will kill you. 也许冒险会让你受伤,但一成不变会让你灭亡. The very theme of the univ ...

  2. Android开发最佳学习路线图(转)

    Android开发总体路线图:  基础学习——JavaSE:        很多朋友一上手就开始学习Android,似乎太着急了一些. Android应用程序开发是以Java语言为基础的,所以没有扎实 ...

  3. linux系统(CentOS)下安装PhantomJS

    1.查看linux系统位数,来判断下载适配的PhantomJS: 输入命令:# lsb_release -a 2.下载PhantomJS: 从官网http://phantomjs.org/downlo ...

  4. IOS ASI和AFN的 区别

    一.底层实现 1> AFN的底层基于OC的NSURLConnection和NSURLSession2> ASI的底层基于纯C语言的CFNetwork框架3> ASI的运行性能 高于 ...

  5. note04-计算机网络

    4.WLAN无线局域网(wareless local area network) IEEE802.11无线以太网协议标准 基础服务集合BSS 基站AP 服务服务集合标识SSID 即wifi名 分配系统 ...

  6. What is a Thread?

    https://computing.llnl.gov/tutorials/pthreads/ Technically, a thread is defined as an independent st ...

  7. ACM-ICPC(10/21)

    写一发后缀数组套路题,看起来简单,写起来要人命哦~~~ 总共13题. 分两天debug吧,有点累了~~~ suffix(后缀数组的应用) sa[i] :排名第 i 的后缀在哪(i 从 1 开始) ra ...

  8. 【[SDOI2009]晨跑】

    板子 题意就是每个点只能经过一次 所以非常显然拆点,除去\(1,n\)每个点\(i\)向\(i'\)连一条容量为\(1\)费用为\(0\)的边 剩下的边按照输入给出的建就好了 代码 #include& ...

  9. 如何写一个FMDB帮助类?看看runtime吧

    FMDB是一个封装很好的sqllite类库.项目中调用的时候只需要写SQL语句,就能实现数据的CURD.我试过即使手写SQL语句也很麻烦,需要一个字段一个字段的拼上去,而且容易出错.有没有动态获取字段 ...

  10. 【洛谷P2947】向右看齐

    向右看齐 题目链接 此题可用单调栈O(n)求解 维护一个单调递减栈,元素从左到右入栈 若新加元素大于栈中元素,则栈中元素的仰望对象即为新加元素 每次将小于新加元素的栈中元素弹出,记录下答案 #incl ...