一 概述

上篇分析了自定义 View 绘制流程及其常用方法:Android View绘制4 Draw过程(上),
本篇将从代码的角度深入分析硬件加速绘制与软件绘制。
通过本篇文章,你将了解到:

1、软件绘制流程
2、硬件加速绘制流程
2、LayerType 对绘制的影响
3、Canvas 从哪里来到哪里去
4、绘制流程全家福

二 软件绘制流程

上篇说过在ViewRootImpl->draw(xx)里软件绘制与硬件加速绘制分道扬镳:

上图是Window 区分硬件加速绘制与软件绘制的入口。
由易到难,先来看看软件绘制流程。

drawSoftware(xx)

#ViewRootImpl.javaprivate boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty, Rect surfaceInsets) {//持有的画布final Canvas canvas;...try {...//申请画布对象,该画布初始大小为dirty的尺寸canvas = mSurface.lockCanvas(dirty);//设置密度canvas.setDensity(mDensity);} catch (Surface.OutOfResourcesException e) {...} catch (IllegalArgumentException e) {...return false;} finally {...}try {//画布是否需要移动canvas.translate(-xoff, -yoff);//mView 即是添加到该Window的RootView//对于Activity、Dialog开启的Window,mView就是我们熟知的DecorView//rootView draw()方法mView.draw(canvas);} finally {try {//提交绘制的内容到Surfacesurface.unlockCanvasAndPost(canvas);} catch (IllegalArgumentException e) {...}}return true;}

以上方法功能重点如下:

1、从Surface 申请Canvas对象,该Canvas为CompatibleCanvas 类型
2、拿到Canvas后,调用View.draw(Canvas)开始绘制RootView
3、整个ViewTree 绘制完成后将内容提交到Surface

注:RootView 只是个代称,并不是某个View的名字。
一些常见的RootView 请移步:Android 输入事件一撸到底之源头活水(1)

关于View.draw(xx)方法在:Android 自定义View之Draw过程(上) 已做过详细分析,结合上述代码,用如下图表示:


可以看得出来,软件绘制有如下特点:

从RootView 递归调用子布局的draw(xx)方法,直到每个符合条件的View都进行了绘制
绘制过程中,所有的View持有相同的Canvas对象
引入问题1:既然所有的View都持有相同的Canvas,那么每个View绘制的起点、终点是如何确定的呢?
该问题稍后分析。

硬件加速绘制流程
概要
软件绘制是将Canvas的一系列操作写入到Bitmap里,而对于硬件加速绘制来说,每个View 都有一个RenderNode,当需要绘制的时候,从RenderNode里获取一个RecordingCanvas,与软件绘制一样,也是调用Canvas一系列的API,只不过调用的这些API记录为一系列的操作行为存放在DisplayList。当一个View录制结束,再将DisplayList交给RenderNode。此时,绘制的步骤已经记录在RenderNode里,到此,针对单个View的硬件绘制完成,这个过程也称作为DisplayList的构建过程。

调用过程分析
来看看硬件加速的入口:

#ThreadedRenderer.javavoid draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {...//(1)--->录制操作//更新根View的DisplayList//从此处开始将绘制操作记录到DisplayList里//最终记录在rootRenderNode里updateRootDisplayList(view, callbacks);//(2)--->渲染//渲染绘制的内容//以proxy为桥梁,而proxy又与rootRenderNode关联//因此最终将上一步记录的绘制操作交给单独的线程渲染int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);...}

重点关注录制操作过程,接着来分析它:

#ThreadedRenderer.javaprivate void updateRootDisplayList(View view, DrawCallbacks callbacks) {//遍历ViewTree,构建DisplayListupdateViewTreeDisplayList(view);//当ViewTree DisplayList构建完毕后//一开始mRootNode 是没有DisplayListif (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {//申请CanvasRecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);try {...//view.updateDisplayListIfDirty() 返回的是RootView 关联的renderNode//现在将RootView renderNode挂到canvas下,这样子就串联起所有的renderNode了canvas.drawRenderNode(view.updateDisplayListIfDirty());...mRootNodeNeedsUpdate = false;} finally {//最后将DisplayList 挂到renderNode下mRootNode.endRecording();}}}private void updateViewTreeDisplayList(View view) {//标记该View已绘制过view.mPrivateFlags |= View.PFLAG_DRAWN;//mRecreateDisplayList --> 表示该View 是否需要重建DisplayList,也就是重新录制,更直白地说是否需要走Draw 过程//若是打上了 PFLAG_INVALIDATED 标记,也就是该View需要刷新,则需要重建view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;//清空原来的值view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;//如果有需要,更新View的DisplayListview.updateDisplayListIfDirty();//View 已经重建完毕,无需再重建view.mRecreateDisplayList = false;}

以上调用了到了View里的方法:updateDisplayListIfDirty()。
顾名思义,如果有需要更新View的DisplayList。

#View.javapublic RenderNode updateDisplayListIfDirty() {//每个View构造的时候都会创建一个RenderNode:mRenderNode,称之为渲染节点final RenderNode renderNode = mRenderNode;//是否支持硬件加速,通过判断View.AttachInfo.mThreadedRendererif (!canHaveDisplayList()) {return renderNode;}//取出该View的标记//1、绘制缓存失效 2、渲染节点还没有DisplayList 3、渲染节点有DisplayList,但是需要更新//三者满足其中一个条件,则进入条件代码块if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0|| !renderNode.hasDisplayList()|| (mRecreateDisplayList)) {//如果有DisplayList且该DisplayList无需更新,则说明该View不需要重新走Draw过程if (renderNode.hasDisplayList()&& !mRecreateDisplayList) {//标记该View已经绘制完成且缓存是有效的mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;//继续查看子布局是否需要构建DisplayListdispatchGetDisplayList(); //---------(1)return renderNode; // no work needed}//上述条件不满足,则说明该View需要构建DisplayListmRecreateDisplayList = true;//layout 过程确定的View的坐标此时用到了int width = mRight - mLeft;int height = mBottom - mTop;//获取当前设置的layerTypeint layerType = getLayerType();//从renderNode里获取Canvas对象,Canvas的尺寸初始化为View的尺寸//该Canvas是RecordingCanvas类型,简单理解为用来录制的Canvasfinal RecordingCanvas canvas = renderNode.beginRecording(width, height);try {//layerType 有三种取值//如果是软件绘制缓存if (layerType == LAYER_TYPE_SOFTWARE) {//---------(2)//则构建缓存buildDrawingCache(true);//实际上就是将绘制操作写入Bitmap里Bitmap cache = getDrawingCache(true);if (cache != null) {//将该Bitmap绘制到Canvas里canvas.drawBitmap(cache, 0, 0, mLayerPaint);}} else {//如果没有设置软件绘制缓存//一般配合Scroller 滑动使用computeScroll();//mScrollX、mScrollY 为滚动的距离//当mScrollX 为正值时,canvas向左移动,绘制的内容往左移动,这也就是为什么明明scroll为正值,为啥View内容往左移的根本原因canvas.translate(-mScrollX, -mScrollY);mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;//---------(3)if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {//该View不需要绘制自身内容(包括内容、前景、背景等)//直接发起绘制子布局的请求dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}if (debugDraw()) {debugDrawFocus(canvas);}} else {//需要绘制自身draw(canvas);}}} finally {//最后结束canvas录制,并将录制产生的结果:DisplayList交给renderNode//---------(4)renderNode.endRecording();setDisplayListProperties(renderNode);}} else {//三个条件不满足,认为已经绘制完成mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;}//返回renderNode 到上一层//---------(5)return renderNode;}

注释里列出了5个比较重要的点,来一一解析:
(1)
dispatchGetDisplayList()
该方法在View里没有实现,在ViewGroup实现如下:

#ViewGroup.javaprotected void dispatchGetDisplayList() {final int count = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < count; i++) {//遍历子布局final View child = children[i];if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {//重建子布局DisplayListrecreateChildDisplayList(child);}}...}private void recreateChildDisplayList(View child) {//判断是否需要重建child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;child.mPrivateFlags &= ~PFLAG_INVALIDATED;//调用子布局重建方法child.updateDisplayListIfDirty();child.mRecreateDisplayList = false;}

可以看出,dispatchGetDisplayList 作用:

遍历子布局,并调用它们的重建方法

这样子,从RootView开始递归调用updateDisplayListIfDirty(),如果子布局需要重建DisplayList,则重新录制绘制操作,否则继续查找子布局是否需要重建DisplayList。

(2)
buildDrawingCache(xx) 用来绘制离屏缓存,后续再细说。

(3)
跳过绘制这段可参考:Android ViewGroup onDraw为什么没调用

(4)
硬件加速绘制有开始、录制、结束的标记:

1、renderNode生成用来绘制的Canvas–> beginRecording,此为开始。
2、调用Canvas.drawXX()–> 录制具体的东西,此为录制过程
3、renderNode结束绘制–> endRecording(),从Canvas里拿到录制的结果:DisplayList,并将该结果赋值给renderNode,此为录制结束

(5)
从第4点可以看出,录制的结果已经存放到RenderNode里,需要将RenderNode返回,该RenderNode将会被挂到父布局的Canvas里,也就是说父布局Canvas已经持有了子布局录制好的DisplayList。

简单一些,用图表示单个View的硬件加速绘制流程:


ViewTree 硬件加速过程:

很明显,硬件加速绘制过程就是构建DisplayList过程,从RootView递归子布局构建DisplayList,当整个DisplayList构建完毕,就可以进行渲染了,渲染线程交给GPU处理,这样子大大解放了CPU工作。

LayerType 对绘制的影响
以上分别阐述了软件绘制与硬件加速绘制的流程,分析的起点是该Window是否支持硬件加速而走不同的分支。
从RootView开始到遍历所有的子孙View,要么都是软件绘制,要么都是硬件加速绘制,如果在硬件加速绘制的中途禁用了某个View的硬件加速会如何表现呢?我们之前提到过通过设置View->LayerType来禁用硬件加速,接下来分析LayerType对绘制流程的影响。
从 Android 自定义View之Draw过程(上)
分析可知:不管软件绘制或者硬件加速绘制,都会走一套公共的流程:

draw(xx)->dispatchDraw(xx)->draw(x1,x2,x3)->draw(xx)...


这也是递归调用的过程。
对于单个View,软件绘制与硬件加速分歧点在哪呢?
答案是:draw(x1,x2,x3)方法

View的软硬绘制分歧点

#View.javaboolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {//canvas是否支持硬件加速//默认canvas是不支持硬件加速的//RecordingCanvas支持硬件加速final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();//是否使用RenderNode绘制,也就是该View是否支持硬件加速//该View支持硬件加速的条件是:canvas支持硬件加速+该Window支持硬件加速boolean drawingWithRenderNode = mAttachInfo != null&& mAttachInfo.mHardwareAccelerated&& hardwareAcceleratedCanvas;//动画相关...if (hardwareAcceleratedCanvas) {//canvas支持硬件加速,需要检测是否需要重建DisplayListmRecreateDisplayList = (mPrivateFlags & PFLAG_INVALIDATED) != 0;mPrivateFlags &= ~PFLAG_INVALIDATED;}RenderNode renderNode = null;Bitmap cache = null;//获取LayerType,View 默认类型是Noneint layerType = getLayerType();//------>(1)if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {//1、设置了离屏软件绘制缓存 2、View不支持硬件加速绘制//两者满足其一if (layerType != LAYER_TYPE_NONE) {//可能设置了软件缓存或者硬件缓存//此时硬件缓存当做软件缓存来使用layerType = LAYER_TYPE_SOFTWARE;//绘制到软件缓存//------>(2)buildDrawingCache(true);}//取出软件缓存cache = getDrawingCache(true);}if (drawingWithRenderNode) { //----->(3)//该View支持硬件加速//则尝试构建DisplayList,并返回renderNoderenderNode = updateDisplayListIfDirty();if (!renderNode.hasDisplayList()) {//一般很少走这renderNode = null;drawingWithRenderNode = false;}}int sx = 0;int sy = 0;if (!drawingWithRenderNode) {computeScroll();//不使用硬件加速时将内容偏移记录sx = mScrollX;sy = mScrollY;}//注意这两个标记,下面会用到//1、存在软件缓存 2、不支持硬件加速 两者同时成立,则说明:使用软件缓存绘制final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;//1、不存在软件缓存 2、不支持硬件加速,两者同时成立,则说明:使用软件绘制final boolean offsetForScroll = cache == null && !drawingWithRenderNode;if (offsetForScroll) {//------>(4)//如果是软件绘制,需要根据View的偏移与内容偏移移动canvas//此时包括内容滚动偏移量canvas.translate(mLeft - sx, mTop - sy);} else {if (!drawingWithRenderNode) {//------>(5)//如果不支持硬件加速,则说明可能是软件缓存绘制//此时也需要位移canvas,只不过不需要考虑内容滚动偏移量canvas.translate(mLeft, mTop);}...}...if (!drawingWithRenderNode) {//不支持硬件加速if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {//裁减canvas,限制canvas展示区域,这就是子布局展示为什么不能超过父布局区域的原因if (offsetForScroll) {//是软件绘制,则裁减掉滚动的距离//------>(6)canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());} else {//否则无需考虑滚动距离if (!scalingRequired || cache == null) {canvas.clipRect(0, 0, getWidth(), getHeight());} else {canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());}}}...}if (!drawingWithDrawingCache) {//不使用软件缓存绘制if (drawingWithRenderNode) {//支持硬件加速mPrivateFlags &= ~PFLAG_DIRTY_MASK;//将该View的renderNode挂到父布局的Canvas下,此处建立了连接((RecordingCanvas) canvas).drawRenderNode(renderNode);} else {//软件绘制,发起了绘制请求:dispatchDraw(canvas) & draw(canvas);//------>(7)if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {mPrivateFlags &= ~PFLAG_DIRTY_MASK;dispatchDraw(canvas);} else {draw(canvas);}}} else if (cache != null) {//软件绘制缓存存在mPrivateFlags &= ~PFLAG_DIRTY_MASK;if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {...//没有设置缓存类型,则将软件绘制缓存写入到canvas的bitmap里canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);} else {...canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);}}...//该View 构建完毕mRecreateDisplayList = false;return more;}

该方法里面的判断比较乱,提取了比较重要的7个点:
(1)
只要设置了离屏软件缓存或者不支持硬件加速,那么就需要使用软件缓存绘制。

(3)
只要支持硬件加速,则使用硬件加速绘制。结合(1),是不是觉得有点矛盾呢?想想满足(1)条件的情况之一:设置了离屏软件缓存,也支持硬件加速,按照(1)的逻辑,那么此时启用了软件缓存绘制。那么(3)继续用硬件加速绘制不是多此一举吗?
回顾一下updateDisplayListIfDirty()里的片段:

        if (layerType == LAYER_TYPE_SOFTWARE) {...//软件缓存绘制buildDrawingCache(true);} else {//硬件绘制...}

这里边再次进行了判断。

(4)(5)
Canvas位移
对于软件绘制,将Canvas进行位移,位移距离考虑了View本身偏移以及View内容偏移。
对于软件缓存绘制,将Canvas进行位移,仅仅考虑了View本身偏移。
对于硬件加速绘制,没看到对Canvas进行位移。
实际上针对软件缓存绘制与硬件加速绘制,Canvas位移既包括View本身偏移也包含了View内容偏移。只是不在上述的代码里。
对于软件缓存绘制:

在buildDrawingCacheImpl(xx) -> canvas.translate(-mScrollX, -mScrollY);进行了内容偏移。

而对于硬件加速绘制:

在layout(xx)->mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom) 进行了View本身的偏移。
在updateDisplayListIfDirty(xx)->canvas.translate(-mScrollX, -mScrollY);进行了内容偏移。

因此,不论软件绘制/软件缓存绘制/硬件加速绘制,三者都对Canvas进行了位移,位移包括:View本身的偏移以及内容的偏移。

以上也解释了问题1。

(6)
Canvas裁减
对于软件绘制,Canvas裁减包括了View内容偏移。
对于软件缓存绘制,Canvas 绘制到Bitmap里。
对于硬件加速绘制,在setDisplayListProperties(xx)->renderNode.setClipToBounds(xx) 进行裁减。
(7)
如果是软件绘制,那么直接调用dispatchDraw(xx)/draw(xx)发起绘制。

draw(x1,x2,x3)方法作用:决定View是使用何种绘制方式:

1、硬件加速绘制
2、软件绘制
3、软件缓存绘制

软件缓存绘制
来看看如何构建软件缓存:

#View.javapublic void buildDrawingCache(boolean autoScale) {//如果缓存标记为失效或者缓存为空if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || (autoScale ?mDrawingCache == null : mUnscaledDrawingCache == null)) {try {//构建缓存buildDrawingCacheImpl(autoScale);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}}private void buildDrawingCacheImpl(boolean autoScale) {int width = mRight - mLeft;int height = mBottom - mTop;...boolean clear = true;Bitmap bitmap = autoScale ? mDrawingCache : mUnscaledDrawingCache;//bitmap 不存在或者bitmap与View尺寸不一致,则创建...Canvas canvas;if (attachInfo != null) {canvas = attachInfo.mCanvas;if (canvas == null) {//第一次,AttachInfo里并没有Canvascanvas = new Canvas();}//关联bitmapcanvas.setBitmap(bitmap);attachInfo.mCanvas = null;} else {//很少走这canvas = new Canvas(bitmap);}computeScroll();final int restoreCount = canvas.save();//根据内容滚动平移canvas.translate(-mScrollX, -mScrollY);mPrivateFlags |= PFLAG_DRAWN;if (mAttachInfo == null || !mAttachInfo.mHardwareAccelerated ||mLayerType != LAYER_TYPE_NONE) {//打上标记,说明软件绘制缓存已生效mPrivateFlags |= PFLAG_DRAWING_CACHE_VALID;}//同样的,调用公共方法if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {mPrivateFlags &= ~PFLAG_DIRTY_MASK;dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}} else {draw(canvas);}canvas.restoreToCount(restoreCount);canvas.setBitmap(null);if (attachInfo != null) {//记录下来,下次创建直接使用attachInfo.mCanvas = canvas;}}

如此一来,软件缓存就构建完成了,其结果存储在Bitmap里,可以通过如下方法获取:

#View.javapublic Bitmap getDrawingCache(boolean autoScale) {//禁止使用软件缓存//默认不禁止if ((mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING) {return null;}if ((mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED) {//是否开启了软件缓存绘制,默认不开启//构建缓存buildDrawingCache(autoScale);}//将缓存返回return autoScale ? mDrawingCache : mUnscaledDrawingCache;}

该方法可用来获取View的页面。
做一个小结:

一开始,硬件加速绘制流程和软件绘制流程各走各的互不影响。
1、使用软件绘制时候,设置了离屏缓存类型:软件缓存,则软件绘制失效,仅仅使用软件缓存绘制。设置了硬件缓存类型也当做软件缓存绘制。
2、使用硬件加速绘制的时候,设置了离屏缓存类型:软件缓存,则硬件加速绘制失效,仅仅使用软件缓存绘制。这也就是为什么设置软件缓存可以禁用硬件加速的原因。
3、软件缓存绘制的结果保存在bitmap里,该Bitmap最终会绘制到父布局的Canvas里。

不管使用哪种绘制类型,都会走共同的调用方法:draw(xx)/dispatchDraw(xx)。
因此,绘制类型对于我们重写onDraw(xx)是透明的。

Canvas 从哪里来到哪里去
软件绘制
从ViewRootImpl->drawSoftware(xx)开始,通过:

canvas = mSurface.lockCanvas(dirty);

生成了Canvas。该Canvas通过View.draw(xx)方法传递给所有的子布局,因此此种情形下,整个ViewTree共享同一个Canvas对象。Canvas类型为:CompatibleCanvas。
硬件加速绘制
从View->updateDisplayListIfDirty(xx)开始,通过:

final RecordingCanvas canvas = renderNode.beginRecording(width, height);

生成了Canvas。可以看出,对于每个支持硬件加速的View都重新生成了Canvas。Canvas类型为:RecordingCanvas。
软件缓存绘制
从View->buildDrawingCacheImpl(xx)开始,通过:

canvas = new Canvas();

生成了Canvas,并将该Canvas记录在AttachInfo里,下次再次构建该View软件缓存时拿出来使用。可以看出,对于每个使用了软件缓存的View都生成了新的Canvas,当然如果AttachInfo有,就可以重复使用。
脱离View的Canvas
以上三者有个共同的特点:所生成的Canvas最终都与Surface建立了联系,因此通过这些Canvas绘制的内容最终能够展示在屏幕上。
那是否可以直接构造脱离View的Canvas呢?答案是可以的。

    private void buildCanvas(int width, int height) {Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas();canvas.setBitmap(bitmap);//绘制canvas.drawXX(xx);...}

如上所示,创建一个Canvas与Bitmap,并将两者关联起来。最后调用Canvas绘制API,绘制的结果将保存在Bitmap里。这个过程实际上也是软件缓存绘制使用的方法。
当然拿到了Bitmap后,我们想让其展示就比较简单了,只要让其关联到View上就可以展示到屏幕上了。关联到View上实际上就是使用View关联的Canvas将生成的Bitmap绘制其上,

绘制流程全家福
用图表示绘制流程:

单纯的软件绘制与硬件加速绘制:
设置了软件缓存时的绘制:

至此,Draw过程系列文章结束。

Android View绘制6 Draw过程(下)相关推荐

  1. 【Android View绘制之旅】Draw过程

    出效果:绘制 经过前面的准备工作 :[Android View绘制之旅]Measure过程,[Android View绘制之旅]Layout过程 我们的视图具备了宽高数据,位置数据,现在到了激动人心的 ...

  2. 【Android View绘制之旅】Layout过程

    1.为什么要进行Layout? 在[Android View绘制之旅]View之测量Measure过程后,View我们得到View的宽高,但光只有宽高值是不足以反映视图的,更需要知道View所在的位置 ...

  3. 【Android View绘制之旅】主脉络

    没搞清楚View绘制原理会怎么样? 只会玩玩初级的组件 看不懂哪些绚丽效果组件实现的原理,即使你有源码 PM会对你很失望,因为有点高级特性你就跪了 当然好的工作机会是没有你的份的 View绘制之旅该怎 ...

  4. android view 绘制过程,深入理解Android中View绘制的三大流程

    前言 最近对Android中View的绘制机制有了一些新的认识,所以想记录下来并分享给大家.View的工作流程主要是指measure.layout.draw这三大流程,即测量.布局和绘制,其中meas ...

  5. Android View绘制之旅

    1.说在起点的话 很早前就想将View绘制原理这块给搞清楚搞透彻,但是奈何自己无知还是愚钝,总未能得真经,所以此次决意好好出发,做到有始有终. 我分析了一下自己的问题,自己实在太功利了,总希望看一两篇 ...

  6. android 绘图流程,Android View绘制流程

    前言 不知道大家有没有想过一个问题,当启动一个Activity的时候,相应的XML布局文件中的View是如何显示到屏幕上的?有些同学会说是通过onMeasure().onLayout().onDraw ...

  7. Android View 绘制流程

    前面讲到 Android View 加载流程,使用 LayoutInflater 将 xml 文件转变成 View ,但是还需要将 View 绘制出来,才能被用户看到,这一过程为绘制流程.由于 And ...

  8. Android View绘制原理解析

    概述 本篇文章主要讲述View是如何在Android源码中产生的,以便于我们能够更好的去自定义一些控件,大体上是按照View绘制的流程来走步骤,在追踪源码之前我们先了解几个基础知识.来看下面的这张图: ...

  9. Android View的加载过程

    2019独角兽企业重金招聘Python工程师标准>>> 大家都知道Android中加载view是从Activity的onCreate方法调用setContentView开始的,那么V ...

  10. android view绘制过程

    应用程序窗口内部所包含的视图对象的实际类型为DecorView.DecorView类继承了View类,是作为容器(ViewGroup)来使用的,它的实现如图1所示: 每一个应用程序窗口的视图对象都有一 ...

最新文章

  1. Inversion Sequence(csu 1555)
  2. C 语言编程 — 堆栈与内存管理
  3. 计算机通信技术 ppt,江苏大学计算机科学与通信工程学院计算机科学系.ppt
  4. bbsmax mysql_Problems with MMM for mysql(译文)
  5. FIFA的完整形式是什么?
  6. 数据结构之顺序栈的一些基本操作
  7. ucos ii 源代码中文注释详解: OS_CPU.c
  8. scikit-learn机器学习常用算法原理及编程实战(五)
  9. 转眼人到中年:前端老程序员无法忘怀的一次百度电话面试(二)
  10. 程序员面试金典——5.6奇偶位交换
  11. 在 Linux 中怎样将 MySQL 迁移到 MariaDB 上
  12. mysql alisql,Mysql-03. ubuntu 安装 alisql
  13. ddr3配置 dsp6678_TMS320C6678外部存储器DDR3硬件设计以及软件调试
  14. 教育平台的线上课程 智能推荐策略
  15. pdf照片显示正常打印时被翻转_要哭了,差点打印不了准考证!(2021考生提前收藏!)...
  16. 计算机网络 | IPv6 | 什么是IPv6
  17. uniapp - APP判断是否开启位置信息服务判断是否授权位置信息权限
  18. 软件版本号讲解:什么是Alpha,Beta,RC,Release
  19. c语言 pow和sqrt注意
  20. SaaS 服务供应商Wynd获3170万美元B轮融资

热门文章

  1. 电子书 UNIX环境高级编程(第3版).pdf
  2. 大话谈VUE之export const
  3. MySQL 的 max_allowed_packet
  4. 加州戴维斯分校计算机专业排名,加州大学戴维斯分校的计算机专业如何?
  5. 近代物理实验 核磁共振的稳态吸收(含数据及参考题)
  6. 不要随便点这个网站,你偷偷下载的小电影,他们全都知道!
  7. 新手,如何快速建立一个网站?
  8. Node+puppeteer学习笔记(五)--API问题解决--使用功能强大的“ eval ”函数
  9. gmail注册方法_如何在Gmail中释放空间:5种回收空间的方法
  10. 互联网思维——极致思维