Android 4.4 Graphic系统详解(4)HWUI概述
![](http://static.blog.csdn.net/images/category_icon.jpg)
1 概述
Android从3.0(API Level 11)开始,在绘制View的时候支持硬件加速,充分利用GPU的特性,使得绘制更加平滑。
实质上就是Android3.0以前,几乎所有的图形绘制都是由Skia完成,Skia是一个向量绘图库,使用CPU来进行运算;所以从Android3.0 开始,Google用hwui取代了Skia,准确的说,是推荐取代,因为Opengl的支持不完全,有少量图形api仍由Skia完成,多数view的绘制通过HWUI模块使用openGL的函数来实现。
由于硬件加速自身并非完美无缺,所以Android提供选项来打开或者关闭硬件加速,默认是关闭。一般我们会在两个级别上打开或者关闭硬件加速:
Application级别:<applicationandroid:hardwareAccelerated="true" ...>
Activity级别:<activity android:hardwareAccelerated="false" ...>
下面我们从代码级别来理解系统是如何实现硬件加速的。
2 开启硬件加速
在开始分析之前,我们先来看下ViewRootImpl中的一个基本流程图:
如上图所示,其实过程2就是打开硬件加速的过程,在ViewRootImpl的SetView函数中,通过调用enableHardwareAcceleration函数来开启硬件加速,我们来看下enableHardwareAcceleration的代码:
- private void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {
- mAttachInfo.mHardwareAccelerated = false;
- mAttachInfo.mHardwareAccelerationRequested = false;
- final boolean hardwareAccelerated =
- (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
- if (hardwareAccelerated) {
- if (!HardwareRenderer.isAvailable()) {
- return;
- }
- // Persistent processes (including the system) should not do
- // accelerated rendering on low-end devices. In that case,
- // sRendererDisabled will be set. In addition, the system process
- // itself should never do accelerated rendering. In that case, both
- // sRendererDisabled and sSystemRendererDisabled are set. When
- // sSystemRendererDisabled is set, PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED
- // can be used by code on the system process to escape that and enable
- // HW accelerated drawing. (This is basically for the lock screen.)
- final boolean fakeHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED) != 0;
- final boolean forceHwAccelerated = (attrs.privateFlags &
- WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED) != 0;
- if (!HardwareRenderer.sRendererDisabled || (HardwareRenderer.sSystemRendererDisabled
- && forceHwAccelerated)) {
- // Don't enable hardware acceleration when we're not on the main thread
- if (!HardwareRenderer.sSystemRendererDisabled &&
- Looper.getMainLooper() != Looper.myLooper()) {
- Log.w(HardwareRenderer.LOG_TAG, "Attempting to initialize hardware "
- + "acceleration outside of the main thread, aborting");
- return;
- }
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.destroy(true);
- }
- final boolean translucent = attrs.format != PixelFormat.OPAQUE;
- mAttachInfo.mHardwareRenderer = HardwareRenderer.createGlRenderer(2, translucent);
- if (mAttachInfo.mHardwareRenderer != null) {
- mAttachInfo.mHardwareRenderer.setName(attrs.getTitle().toString());
- mAttachInfo.mHardwareAccelerated =
- mAttachInfo.mHardwareAccelerationRequested = true;
- }
- } else if (fakeHwAccelerated) {
- // The window had wanted to use hardware acceleration, but this
- // is not allowed in its process. By setting this flag, it can
- // still render as if it was accelerated. This is basically for
- // the preview windows the window manager shows for launching
- // applications, so they will look more like the app being launched.
- mAttachInfo.mHardwareAccelerationRequested = true;
- }
- }
- }
上面代码不长,而且注释很完善,所以我们主要来看下函数的最主体部分即可。
- static HardwareRenderer createGlRenderer(int glVersion, boolean translucent) {
- switch (glVersion) {
- case 2:
- return Gl20Renderer.create(translucent);
- }
- throw new IllegalArgumentException("Unknown GL version: " + glVersion);
- }
- static HardwareRenderer create(boolean translucent) {
- if (GLES20Canvas.isAvailable()) {
- return new Gl20Renderer(translucent);
- }
- return null;
- }
- GlRenderer(int glVersion, boolean translucent) {
- mGlVersion = glVersion;
- mTranslucent = translucent;
- loadSystemProperties(null);
- }
我们发现这段代码最主要的作用就是创建了一个Gl20Renderer(继承自GlRenderer和HardwareRenderer)。创建时调用了通过loadSystemProperties函数加载了一些属性和调试选项,这里不展开,有兴趣可以详细研究一下。
创建好的Gl20Renderer保存在了mAttachInfo.mHardwareRenderer中,后面我们在使用硬件加速时就会用到。
3 硬件绘制
从前面图中我们可以知道,在ViewRootImpl绘制时,调用了draw函数。有两种选择,一种是软件绘制,一种是硬件绘制。
- if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) {
- // Draw with hardware renderer.
- mIsAnimating = false;
- mHardwareYOffset = yoff;
- mResizeAlpha = resizeAlpha;
- mCurrentDirty.set(dirty);
- dirty.setEmpty();
- attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,
- animating ? null : mCurrentDirty);
- } 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 (attachInfo.mHardwareRenderer != null &&
- !attachInfo.mHardwareRenderer.isEnabled() &&
- attachInfo.mHardwareRenderer.isRequested()) {
- try {
- attachInfo.mHardwareRenderer.initializeIfNeeded(mWidth, mHeight,
- mHolder.getSurface());
- } catch (OutOfResourcesException e) {
- handleOutOfResourcesException(e);
- return;
- }
- mFullRedrawNeeded = true;
- scheduleTraversals();
- return;
- }
- if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
- return;
- }
- }
- }
软件绘制不在我们分析范围内,这里我们将在下一小节中来研究一下attachInfo.mHardwareRenderer.draw(mView, attachInfo, this,animating ? null : mCurrentDirty)这个调用。
我们前面已经知道mHardwareRenderer其实是一个Gl20Renderer,那么我们来看下它的draw函数。
- void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
- Rect dirty) {
- if (canDraw()) {
- if (surfaceState != SURFACE_STATE_ERROR) {
- HardwareCanvas canvas = mCanvas;
- attachInfo.mHardwareCanvas = canvas;
- dirty = beginFrame(canvas, dirty, surfaceState);
- DisplayList displayList = buildDisplayList(view, canvas);
- int saveCount = 0;
- int status = DisplayList.STATUS_DONE;
- try {
- status = prepareFrame(dirty);
- if(mSurface.isValid() && dirty != null) {
- mSurface.setDirtyRect(dirty);
- }
- saveCount = canvas.save();
- callbacks.onHardwarePreDraw(canvas);
- if (displayList != null) {
- status |= drawDisplayList(attachInfo, canvas, displayList, status);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "An error has occurred while drawing:", e);
- } finally {
- callbacks.onHardwarePostDraw(canvas);
- canvas.restoreToCount(saveCount);
- view.mRecreateDisplayList = false;
- if (mDrawDelta > 0) {
- mFrameCount++;
- debugOverdraw(attachInfo, dirty, canvas, displayList);
- debugDirtyRegions(dirty, canvas);
- drawProfileData(attachInfo);
- }
- }
- onPostDraw();
- swapBuffers(status);
- }
- }
- }
上面这几十行代码,就是硬件绘制的最重要的几个流程调用,我们看到draw函数主要分为如下几步,beginFrame,buildDisplayList,prepareFrame,drawDisplayList,onHardwarePostDraw,onPostDraw,swapBuffers。整个详细的调用过程如下图:
从上图中我们可以看出,整个内部详细的调用过程还是比较复杂的,我们这里仔细的一一讲解一下。
1 beginFrame
这个函数的作用很简单:Notifies EGL that the frame is about to be rendered。
功能主要通过android_view_HardwareRenderer_beginFrame这个JNI函数实现:
- static void android_view_HardwareRenderer_beginFrame(JNIEnv* env, jobject clazz,
- jintArray size) {
- EGLDisplay display = eglGetCurrentDisplay();
- EGLSurface surface = eglGetCurrentSurface(EGL_DRAW);
- if (size) {
- EGLint value;
- jint* storage = env->GetIntArrayElements(size, NULL);
- eglQuerySurface(display, surface, EGL_WIDTH, &value);
- storage[0] = value;
- eglQuerySurface(display, surface, EGL_HEIGHT, &value);
- storage[1] = value;
- env->ReleaseIntArrayElements(size, storage, 0);
- }
- eglBeginFrame(display, surface);
在BeginFrame() 里获取EGLDisplay(用于显示) 和一个EGLSurface,OpenGL将在这个Surface上进行绘图。
eglBeginFrame主要是校验参数的合法性。
2 buildDisplayList
buildDisplayList是一个很重要也比较复杂的函数,通过这个函数,我们真正完成了视图绘制的录制工作,并将操作指令保存到了native层的DisplayList中,我们这里重点讲一下。
- private DisplayList buildDisplayList(View view, HardwareCanvas canvas) {
- if (mDrawDelta <= 0) {//如果根本没有新的绘制发生,使用之前的displaylist即可
- return view.mDisplayList;
- }
- view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
- == View.PFLAG_INVALIDATED;
- view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
- long buildDisplayListStartTime = startBuildDisplayListProfiling();
- canvas.clearLayerUpdates();
- Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
- DisplayList displayList = view.getDisplayList();
- Trace.traceEnd(Trace.TRACE_TAG_VIEW);
- endBuildDisplayListProfiling(buildDisplayListStartTime);
- return displayList;
- }
其实主要逻辑是通过view类的getDisplayList()方法实现的,这个方法通过递归的方式获得了整个View树的DisplayList:
- private DisplayList getDisplayList(DisplayList displayList, boolean isLayer) {
- if (((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 ||
- displayList == null || !displayList.isValid() ||
- (!isLayer && mRecreateDisplayList))) {
- // Don't need to recreate the display list, just need to tell our
- // children to restore/recreate theirs
- if (displayList != null && displayList.isValid() &&
- !isLayer && !mRecreateDisplayList) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- dispatchGetDisplayList();
- return displayList;
- }
- if (!isLayer) {
- // If we got here, we're recreating it. Mark it as such to ensure that
- // we copy in child display lists into ours in drawChild()
- mRecreateDisplayList = true;
- }
- if (displayList == null) {
- displayList = mAttachInfo.mHardwareRenderer.createDisplayList(getClass().getName());
- // If we're creating a new display list, make sure our parent gets invalidated
- // since they will need to recreate their display list to account for this
- // new child display list.
- invalidateParentCaches();
- }
- boolean caching = false;
- int width = mRight - mLeft;
- int height = mBottom - mTop;
- int layerType = getLayerType();
- final HardwareCanvas canvas = displayList.start(width, height);
- ...
上面函数相当长,我们首先分析前半部分。暂时只看displaylist新创建时会走的逻辑:函数前半部分的流程基本是createDisplayList,invalidateParentCaches,displayList.start,我们来一点点分析。
2.1 createDisplayList
首先我们要创建一个displaylist,这里实际上只是创建了一个GLES20DisplayList类。
- public DisplayList createDisplayList(String name) {
- return new GLES20DisplayList(name);
- }
GLES20DisplayList是DisplayList的子类,这是一个影子类,没有实际用途,主要用来管理Native层的DisplayList的销毁。Native层的DisplayList在后面的start函数中才被真正创建。
2.2 invalidateParentCaches
这个函数的作用主要是,一旦我们重建了DisplayList,那么这个view必须清空之前的缓存,我们通过这个函数调用来清空缓存。
- protected void invalidateParentCaches() {
- if (mParent instanceof View) {
- ((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
- }
- }
2.3 displayList.start
下面看下displayList.start函数:
- public HardwareCanvas start(int width, int height) {
- mValid = false;
- mCanvas = GLES20RecordingCanvas.obtain(this);
- mCanvas.start();
- mCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- mCanvas.onPreDraw(null);
- return mCanvas;
- }
2.3.1 GLES20RecordingCanvas.obtain
start函数真正开始构建DisplayList的上下文,先来看下obtain函数:
- static GLES20RecordingCanvas obtain(GLES20DisplayList displayList) {
- GLES20RecordingCanvas canvas = sPool.acquire();
- if (canvas == null) {
- canvas = new GLES20RecordingCanvas();
- }
- canvas.mDisplayList = displayList;
- return canvas;
- }
obtain函数的作用从名字中就可以看出来,主要作用是获取到一个GLES20RecordingCanvas,这个类是 HardwareCanvas和GLES20Canvas的子类。
HardwareCanvas是一个抽象类,如果系统属性和应用程序指定使用硬件加速(现已成为默认),它将会被View(通过AttachInfo,如 下图所示) 引用来完成所有的图形绘制工作。GLES20Canvas 则是hardwareCanvas的实现,但它也只是一层封装而已,真正的实现在Native 层,通过jni (andriod_view_gles20Canvas.cpp)接口来访问底层的Renderer, 进而执行OpenGL的命令。 此外,GLES20Canvas还提供了一些静态接口,用于创建各类Renderer对象。
GLES20RecordingCanvas 继承GLES20Canvas, 通过它调用的OpenGL命令将会存储在DisplayList里面,而不会立即执行。
- protected GLES20Canvas(boolean record, boolean translucent) {
- mOpaque = !translucent;
- if (record) {
- mRenderer = nCreateDisplayListRenderer();
- } else {
- mRenderer = nCreateRenderer();
- }
- setupFinalizer();
- }
我们看到GLES20Canvas在创建时,由于我们现在需要录制命令,所以我们这里调用了nCreateDisplayListRenderer函数,创建的真正的用于渲染的本地对象DisplayListRenderer:
- static OpenGLRenderer* android_view_GLES20Canvas_createDisplayListRenderer(JNIEnv* env,
- jobject clazz) {
- return new DisplayListRenderer;
- }
DisplayListRenderer是OpenGLRenderer的子类,相比于OpenGLRenderer是直接绘制,DisplayListRenderer则是将绘制命令保存在它内部的mDisplayListData中,mDisplayListData的类型是DisplayListData:
- class DisplayListData : public LightRefBase<DisplayListData> {
- public:
- LinearAllocator allocator;
- Vector<DisplayListOp*> displayListOps;
- };
我们看到这里的重点必然是这个容器displayListOps,一定是它存储了绘制命令DisplayListOp,至于DisplayListOp的详细内容,我们后面再展开。
这样,通过nCreateDisplayListRenderer函数,我们创建了一个本地的DisplayListRenderer对象,他会将绘制命令保存在一个DisplayListData类的DisplayListOp容器里。
现在我们再次回到GLES20Canvas的创建过程中来,调用完nCreateDisplayListRenderer后,程序又继续调用了setupFinalizer,这个我们从名字就可以猜出来这是用于这个canvas回收的,这里我们就不再展开。
2.3.2 GLES20RecordingCanvas.start
回到displayList.start函数中来,创建好GLES20RecordingCanvas之后,继续调用了它的start函数。这个函数主要是清空自己和它的孩子的所有display的引用,这里不再展开。
2.3.3 GLES20RecordingCanvas.setViewport
继续调用setViewport来设置视口:
- public void setViewport(int width, int height) {
- mWidth = width;
- mHeight = height;
- nSetViewport(mRenderer, width, height);
- }
- static void android_view_GLES20Canvas_setViewport(JNIEnv* env, jobject clazz,
- OpenGLRenderer* renderer, jint width, jint height) {
- renderer->setViewport(width, height);
- }
- void DisplayListRenderer::setViewport(int width, int height) {
- mOrthoMatrix.loadOrtho(0, width, height, 0, -1, 1);
- mWidth = width;
- mHeight = height;
- }
- void Matrix4::loadOrtho(float left, float right, float bottom, float top, float near, float far) {
- loadIdentity();
- data[kScaleX] = 2.0f / (right - left);
- data[kScaleY] = 2.0f / (top - bottom);
- data[kScaleZ] = -2.0f / (far - near);
- data[kTranslateX] = -(right + left) / (right - left);
- data[kTranslateY] = -(top + bottom) / (top - bottom);
- data[kTranslateZ] = -(far + near) / (far - near);
- mType = kTypeTranslate | kTypeScale | kTypeRectToRect;
- }
我们看到上层调用了视口,下层实际上还是通过DisplayListRenderer做了具体实现,主要是做了一个正交投影,具体计算我们这里暂时先不研究。
其实大多数opengl在Canvas层面上的调用都是这样,底层通过render实现了类似OpenGl的实现,后面类似函数我们将不再展开。
2.3.4 GLES20RecordingCanvas.onPreDraw
GLES20RecordingCanvas.onPreDraw函数其实也很简单,先来看代码:
- public int onPreDraw(Rect dirty) {
- if (dirty != null) {
- return nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom,
- mOpaque);
- } else {
- return nPrepare(mRenderer, mOpaque);
- }
- }
- static int android_view_GLES20Canvas_prepare(JNIEnv* env, jobject clazz,
- OpenGLRenderer* renderer, jboolean opaque) {
- return renderer->prepare(opaque);
- }
- status_t OpenGLRenderer::prepare(bool opaque) {
- return prepareDirty(0.0f, 0.0f, mWidth, mHeight, opaque);
- }
- status_t OpenGLRenderer::prepareDirty(float left, float top,
- float right, float bottom, bool opaque) {
- setupFrameState(left, top, right, bottom, opaque);
- // Layer renderers will start the frame immediately
- // The framebuffer renderer will first defer the display list
- // for each layer and wait until the first drawing command
- // to start the frame
- if (mSnapshot->fbo == 0) {
- syncState();
- updateLayers();
- } else {
- return startFrame();
- }
- return DrawGlInfo::kStatusDone;
- }
本身这个onPreDraw其实是很简单的,无非是通过canvas来设置要显示的脏区域。最终逻辑依然是通过native层的OpenGLRenderer来实现,当然,这个实现最后变得有点复杂,因为这里又引入了一个新的概念,SnapShot:
- void OpenGLRenderer::setupFrameState(float left, float top,
- float right, float bottom, bool opaque) {
- mCaches.clearGarbage();
- mOpaque = opaque;
- mSnapshot = new Snapshot(mFirstSnapshot,
- SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
- mSnapshot->fbo = getTargetFbo();
- mSaveCount = 1;
- mSnapshot->setClip(left, top, right, bottom);
- mTilingClip.set(left, top, right, bottom);
- }
我们这章引入的新概念实在过多,因此我们这里先跳过这个概念,可以参考这篇博客来先了解一下这个快照的概念,我们后面将单独开章节讲这个问题。
2.4 getDisplayList中段小结
由于getDisplayList实在是逻辑比较长,涉及的新概念比较多,我们这里暂时先暂停一下,回顾下前半部分的逻辑::函数前半部分的流程基本是createDisplayList,invalidateParentCaches,displayList.start。createDisplayList的作用是创建了一个java层的GLES20DisplayList,而displayList.start重点则是创建了一个native层GLES20RecordingCanvas,在创建Canvas的同时创建了DisplayListRenderer,DisplayListRenderer将需要执行的命令保存在内部的displaylist列表中,然后设置了视口,设置了脏区域,这里我们跳过了Snapshot这个新概念。
下面我们继续来看下getDisplayList后半段的代码。
- {
- ...
- try {
- if (!isLayer && layerType != LAYER_TYPE_NONE) {
- if (layerType == LAYER_TYPE_HARDWARE) {
- final HardwareLayer layer = getHardwareLayer();
- if (layer != null && layer.isValid()) {
- canvas.drawHardwareLayer(layer, 0, 0, mLayerPaint);
- } else {
- if (layer != null && !layer.isValid()) Log.e("ViewSystem", "View #2 getHardwareLayer() is not valid.");
- canvas.saveLayer(0, 0, mRight - mLeft, mBottom - mTop, mLayerPaint,
- Canvas.HAS_ALPHA_LAYER_SAVE_FLAG |
- Canvas.CLIP_TO_LAYER_SAVE_FLAG);
- }
- caching = true;
- } else {
- buildDrawingCache(true);
- Bitmap cache = getDrawingCache(true);
- if (cache != null) {
- canvas.drawBitmap(cache, 0, 0, mLayerPaint);
- caching = true;
- }
- }
- } else {
- computeScroll();
- canvas.translate(-mScrollX, -mScrollY);
- if (!isLayer) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
- }
- ...
2.5 开始生成绘制命令
上面这段代码涉及到两条分支,第一条分支又涉及到了新概念,layerType和HardwareLayer,这条分支比较复杂,我们单独放在下一章中单独讲解,我们现在重点看下第二条分支的代码:
- {
- ...
- else {
- computeScroll();
- canvas.translate(-mScrollX, -mScrollY);
- if (!isLayer) {
- mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
- mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- }
- // Fast path for layouts with no backgrounds
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
- }
computeScroll计算滚动位置,和Graphic关系不大,不展开。
translate函数我们在上一节 Android 4.4 Graphic系统详解(4) 一个view的绘制之旅 中已经讲过了,这里不再展开。
其实就是创建了一个Op类的对象,然后将它保存在了DisplayListData的displayListOps容器里面,便于后面的执行。
回到view类getdisplaylist函数中来,说完了translate,下面就是重要的绘制生成流程了。
- {
- if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
- dispatchDraw(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().draw(canvas);
- }
- } else {
- draw(canvas);
- }
- }
其中dispatchDraw是通知view的所有孩子进行绘制(?),其实流程应该都是类似的,因此我们直接看当前view的draw流程,此函数将把View自身及其所有的子子孙孙绘制到给定的画布上。其画图过程主要分为以下六步:
1) 画背景
2) 如果需要,保存画布的层为未来的淡入淡出做好准备
3) 画View本身的内容
4) 画它的孩子
5) 如果需要,画淡入淡出的边缘并恢复层
6) 画装饰部分(如:滚动条)
上述六步中,2,5步常规情况下不需要,因此我们暂时跳过,只重点分析1,3,6步。
- public void draw(Canvas canvas) {
- // Step 1, draw the background, if needed
- int saveCount;
- if (!dirtyOpaque) {
- final Drawable background = mBackground;
- if (background != null) {
- final int scrollX = mScrollX;
- final int scrollY = mScrollY;
- if (mBackgroundSizeChanged) {
- background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
- mBackgroundSizeChanged = false;
- }
- if ((scrollX | scrollY) == 0) {
- background.draw(canvas);
- } else {
- canvas.translate(scrollX, scrollY);
- background.draw(canvas);
- canvas.translate(-scrollX, -scrollY);
- }
- }
- }
- // skip step 2 & 5 if possible (common case)
- final int viewFlags = mViewFlags;
- boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
- boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
- if (!verticalEdges && !horizontalEdges) {
- // Step 3, draw the content
- if (!dirtyOpaque) onDraw(canvas);
- // Step 4, draw the children
- dispatchDraw(canvas);
- // Step 6, draw decorations (scrollbars)
- onDrawScrollBars(canvas);
- if (mOverlay != null && !mOverlay.isEmpty()) {
- mOverlay.getOverlayView().dispatchDraw(canvas);
- }
- // we're done...
- return;
- }
- }
2.5.1 绘制背景
绘制背景很简单,只是调用了 background.draw(canvas)函数,其中background是一个Drawable类型,关于Drawable的绘制,依然可以参考“一个view的绘制之旅”一章中的Drawable的绘制小节讲解,这里不再展开。
2.5.2 onDraw 绘制内容
这一过程比较复杂,其实也就是我们在上一章“一个view的绘制之旅”中讲到的全部内容,具体内容可参考上一章。
2.5.3 绘制滚动条
绘制滚动条的代码比较长,但是按照我们一贯的习惯,我们只关注和Graphic相关的部分,在onDrawScrollBars函数中有横向和纵向ScrollBar的区别,但是对于下层的Graphic并无这种区别,都是调用ScrollBarDrawable的draw函数:
- public void draw(Canvas canvas) {
- boolean drawTrack = true;
- Rect r = getBounds();
- if (drawTrack) {
- drawTrack(canvas, r, vertical);
- }
- if (drawThumb) {
- int size = vertical ? r.height() : r.width();
- int thickness = vertical ? r.width() : r.height();
- int length = Math.round((float) size * extent / range);
- int offset = Math.round((float) (size - length) * mOffset / (range - extent));
- drawThumb(canvas, r, offset, length, vertical);
- }
- }
drawThumb中逻辑转来转去,最终其实还是在drawThumb中再次调用了Drawable的draw函数,这里不再详述。
最终,通过漫长的过程,我们终于完成了displaylist的构建,在完成了buildDisplayList的调用之后,我们回到Gl20Renderer类,继续来看下的draw函数,接下来就该Displaylist的真正执行了。