[Android FrameWork 6.0源码学习] View的重绘过程之Draw
View绘制的三部曲,测量,布局,绘画
现在我们分析绘画部分
测量和布局 在前两篇文章中已经分析过了。不了解的可以去我的博客里找一下
下面进入正题,开始分析调用以及函数原理
private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
//调用内部实现方法,来实现分发绘画的工作
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//...
}
这个函数调用内部draw方法去处理绘画前的工作,来继续完成绘制工作
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (DEBUG_FPS) {
trackFPS();
}
//出发绘制监听
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
//当界面需要滚动的时候,这个方法会触发Scroller类下的startScroll函数
scrollToRectOrFocus(null, false);
//如果界面发生了滚动,就分发滚动监听
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
if (mResizeBuffer != null) {
long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
if (deltaTime < mResizeBufferDuration) {
float amt = deltaTime/(float) mResizeBufferDuration;
amt = mResizeInterpolator.getInterpolation(amt);
animating = true;
resizeAlpha = 255 - (int)(amt*255);
} else {
disposeResizeBuffer();
}
}
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating) {
if (mScroller != null) {
mScroller.abortAnimation();
}
disposeResizeBuffer();
}
return;
}
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
//如果有注册TreeObserver下的监听,在调用onDraw之前会触发
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = 0;
int yOffset = curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
final boolean hasFocus = getAccessibilityFocusedRect(bounds);
if (!hasFocus) {
bounds.setEmpty();
}
if (!bounds.equals(drawable.getBounds())) {
accessibilityFocusDirty = true;
}
}
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
mResizeAlpha = resizeAlpha;
if (invalidateRoot) {
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
dirty.setEmpty();
mBlockResizeBuffer = false;
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (mAttachInfo.mHardwareRenderer != null &&
!mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.isRequested()) {
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
//在这个drawSOftWare方法中会调用view的draw方法,之后整个绘画流程就跑起来了
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
这个函数在调用view的draw之前做了很多处理,大概总结一下就是view的滚动设置和硬件加速功能绘画还有一些比较深入的属性比如插图之类,这些我们不怎么关心
我们最关心的是 drawSoftWare 方法
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//获取一块画布,这块画布会传递到各个onDraw方法中
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
// TODO: Do this in native
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not lock surface", e);
// Don't assume this is due to out of memory, it could be
// something else, and if it is something else then we could
// kill stuff (or ourself) for no reason.
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
+ canvas.getWidth() + ", h=" + canvas.getHeight());
//canvas.drawARGB(255, 255, 0, 0);
}
// If this bitmap's format includes an alpha channel, we
// need to clear it before drawing so that the child will
// properly re-composite its drawing on a transparent
// background. This automatically respects the clip/dirty region
// or
// If we are applying an offset, we need to clear the area
// where the offset doesn't appear to avoid having garbage
// left in the blank areas.
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
if (DEBUG_DRAW) {
Context cxt = mView.getContext();
Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
", metrics=" + cxt.getResources().getDisplayMetrics() +
", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
}
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//从这开始触发整个view树的绘制
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
if (LOCAL_LOGV) {
Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
}
}
return true;
}
这个方法的作用就是先从系统的Surface中获取一块画布,设置好画布的属性后传递到DecorView的draw方法中,从而激活整个view的绘画流程
@CallSuper
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;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
//第一步,绘制背景
//在第一步中,在drawBackground中会根据不同类型的背景,去调用不同类型下的draw方法
//比如,背景是一个BitmapDrawable,那么就会调用BitmapDrawable的draw方法,这些都是用Drawable这个抽象类编写的
//每种不同的背景都是集成自Drawable,这也就是面向抽象/面向接口编程的好处,可以处理很多种情况
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// Fading Edge是View很神奇的一个效果,大家可以自己尝试一下
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方法,让ViewGroup遍历并调用所有的onDraw方法,整个view绘画流程被激活
dispatchDraw(canvas);
// 如果设置了 Overlay ,就调用并绘制 Overlay
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 绘制前景图
onDrawForeground(canvas);
// we're done...
return;
}
/*
* 后边是对于Fading Edge效果的设置,这次就不再分析了,有兴趣的朋友可以自己看一下这个效果
*
*/
}
剩下的流程就是view树中各个子view自己的绘制流程了,然后ViewRootImpl在拿着绘制好的画布
View的三部曲到此就分析完了,分析的不是那么细致,但是最少大体流程还是抛出来了。
在那遥远的ViewRootImpl中定义了一个叫做performTraversals函数,这个函数负责屏幕的显示工作
首先是由PhoneWindow来制作一个DecorView出来,在由WindowManager创建所对应的ViewRootImpl出来,然后调用ViewRootImpl下的setView函数
setView函数会触发requestLayout函数,这个函数会触发performTraversals函数,最终我们view的onMeasure onLayout onDraw都会被调用,从而完成了整个view的重绘过程
当屏幕上的任何一个地方发生变化。都是调用performTraversals来完成了。比如:钟表的分钟发生改变等等....有兴趣的朋友最好是自己在研读一遍源码,能学到很多有意思的功能
[Android FrameWork 6.0源码学习] View的重绘过程之Draw的更多相关文章
- [Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法
博客首页:http://www.cnblogs.com/kezhuang/p/关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下<[Andr ...
- [Android FrameWork 6.0源码学习] View的重绘过程之Layout
View绘制的三部曲,测量,布局,绘画现在我们分析布局部分测量部分在上篇文章中已经分析过了.不了解的可以去我的博客里找一下 View的布局和测量一样,都是从ViewRootImpl中发起,ViewRo ...
- [Android FrameWork 6.0源码学习] View的重绘过程
View绘制的三部曲, 测量,布局,绘画今天我们分析测量过程 view的测量是从ViewRootImpl发起的,View需要重绘,都是发送请求给ViewRootImpl,然后他组织重绘在重绘的过程中 ...
- [Android FrameWork 6.0源码学习] View的重绘ViewRootImpl的setView方法
博客首页:http://www.cnblogs.com/kezhuang/p/ 本篇文章来分析一下WindowManager的后续工作,也就是ViewRootImpl的setView函数的工作 /i* ...
- [Android FrameWork 6.0源码学习] LayoutInflater 类分析
LayoutInflater是用来解析XML布局文件,然后生成对象的ViewTree的工具类.是这个工具类的存在,才能让我们写起Layout来那么省劲. 我们接下来进去刨析,看看里边的奥秘 //调用i ...
- [Android FrameWork 6.0源码学习] ViewGroup的addView函数分析
Android中整个的View的组装是采用组合模式. ViewGroup就相当与树根,各种Layout就相当于枝干,各种子View,就相当于树叶. 至于View类.我们就当它是个种子吧.哈哈! Vie ...
- [Android FrameWork 6.0源码学习] Window窗口类分析
了解这一章节,需要先了解LayoutInflater这个工具类,我以前分析过:http://www.cnblogs.com/kezhuang/p/6978783.html Window是Activit ...
- Android View的重绘过程之Draw
博客首页:http://www.cnblogs.com/kezhuang/p/ View绘制的三部曲,测量,布局,绘画现在我们分析绘画部分测量和布局 在前两篇文章中已经分析过了.不了解的可以去我的博客 ...
- Android View的重绘过程之WindowManager的addView方法
博客首页:http://www.cnblogs.com/kezhuang/p/ 关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下 <[An ...
随机推荐
- 遇到local variable 'e' referenced before assignment这样的问题应该如何解决
问题:程序报错:local variable 'e' referenced before assignment 解决:遇到这样的问题,说明你在声明变量e之前就已经对其进行了调用,定位到错误的地方,对变 ...
- CSS 中的rem,em,vh,vw一次说清楚
关于css中的长度单位,我们用的最多就是px,因为他简单直接.但是当一套方案匹配不同终端时,px就会显得过于生硬,不容易变通. 然而rem,em,vh,vw就可以有效的解决这一问题.让我们来看看这些东 ...
- SQL 模糊查询
在进行数据库查询时,有完整查询和模糊查询之分.一般模糊查询语句如下: SELECT 字段 FROM 表 WHERE 某字段 Like 条件 其中关于条件,SQL提供了四种匹配模式:1,%:表示任意0个 ...
- EasyUI Datagrid 鼠标悬停显示单元格内容
第一种方式: .js 定义函数 <script type="text/javascript"> //格式化单元格提示信息 function formatCellTool ...
- JS中的运算符 以及变量和输入输出
1.算术运算(单目运算符) + 加 .- 减.* 乘. / 除. % 取余.++ 自增.-- 自减. >>> +:有两种作用,链接字符串/加法运算,当+两边全为数字时,进行加法运算, ...
- [原创]在HP DL380 G7服务器上部署基于Ubuntu Server 16.04 和 VirtualBox的云平台
对于一线开发人员来说,一提到虚拟机平台,往往会让人联想到在价格昂贵的服务器上部署VMware vSphere之类软件来实现. 笔者作为一个资深码农,也是一直梦寐着在自己家中打造一个真正的家庭私有云,秒 ...
- Java入门(2) —— 变量详解、运算符、定义类和定义方法以及方法的调用
1.变量 1.定义变量 1.声明的同时直接赋值 数据类型 变量名 = 值; 2.先声明,后赋值 声明: 数据类型 变量名; 赋值: 变量名 = 值; 2.数据类型 基本数据类型:4类8种 整数 --- ...
- Electron 实战桌面计算器应用
前言 Electron 是一个搭建跨平台桌面应用的框架,仅仅使用 JavaScript.HTML 以及 CSS,即可快速而容易地搭建一个原生应用.这对于想要涉及其他领域的开发者来说是一个非常大的福利. ...
- linux(十三)之磁盘分区、创建文件系统、挂载
前面学习了linux的用户管理 ,感觉是不是多lnux的多用户多任务的系统感觉十分了解了,但是其实并不然的.你还需要了解更多.接下来给大家分享的是 在vmware中添加硬盘创建分区,然后挂载到指定目录 ...
- [REST] 1.REST的起源
0. 世界上第一个网站 1990年12月20日,这一天对于现在的互联网来说意义非凡.欧洲核子研究组织(CREN)的科学家Tim Berners-Lee在一台NeXT电脑上启动了世界上的第一个网站(当然 ...