天天看點

Android元件體系之視圖繪制

一、View元件

View元件有幾個重要的方法需要關注,也是自定義View經常需要重寫的方法。

1、measure

作用是測量View元件的尺寸。對應的方法是onMeasure,測量View的寬和高。View和 ViewGroup都有measure方法,但ViewGroup除了測量自身尺寸,還要周遊地調用子元素的measure方法。

2、layout

用于确定布局位置。對應的方法是layout、onLayout,用于确定元素的位置,ViewGroup中的layout方法用來确定子元素的位置。

3、draw

作用是繪制内容背景,包括View的内容、子View的内容和背景。具體方法包括:drawBackground、onDraw、ViewGroup.dispatchDraw。

這個方法有個關鍵的入參也是唯一的入參:Canvas。其在native層有個對應的畫布元件SkCanvas,該元件内部封裝的SkBitmap實作了類似畫紙的功能。

在Android系統中,View.draw的部分實作如下:

// Step 1, draw the background, if needed
        int saveCount;       
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // 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) {
                long logTime = System.currentTimeMillis();
                onDraw(canvas);                
            }
            ...

            // Step 4, draw the children
            dispatchDraw(canvas);
            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);
           

其中繪制背景的實作,是通過Drawable類型的變量mBackground也就是背景圖來完成的。View中的onDraw是個空方法,但在具體的View元件例如ImageView中,都會有對應的擴充實作。

這裡的繪制,主體功能通常是Drawable和Canvas,後者處理繪制區域、矩陣變換等工作,前者根據元件功能,實作具體繪制操作,例如BitmapDrawable中的draw方法,最終是通過Paint畫筆元件和Canvas的drawBitmap方法實作繪制操作的。

對于自定義視圖元件,通常需要重寫onDraw方法,并在該方法中調用Canvas的drawXXX例如drawBitmap、drawText等方法來實作預期的效果,如果涉及到幾何變換,還可能會用到Matrix元件。

二、Canvas元件

作為繪制進行中的畫布元件,Canvas提供了裁剪區域/路徑和多種圖形/圖像繪制功能,對應的方法有:

  •  clipRect、clipPath、clipRegion;
  •  drawBitmap、drawTex、drawLine;

具體實作都是在native層完成的,涉及SkiaCanvas、SkCanvas。

    public boolean clipRect(@NonNull RectF rect, @NonNull Region.Op op) {
        checkValidClipOp(op);
        return nClipRect(mNativeCanvasWrapper, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
    }
           

在framework層的android_graphics_Canvas.cpp中,有對應實作:

static jboolean clipRect(jlong canvasHandle, jfloat l, jfloat t,
                         jfloat r, jfloat b, jint opHandle) {
    bool nonEmptyClip = get_canvas(canvasHandle)->clipRect(l, t, r, b,
            opHandleToClipOp(opHandle));
    return nonEmptyClip ? JNI_TRUE : JNI_FALSE;
}
           

該方法會調用到SkiaCanvas.cpp中的實作,這裡先不展開分析。

三、融合與思考

1、Activity、Window和View之間關系

1)Window和PhoneWindow

PhoneWindow類,派生于Window,是連接配接Activity跟View的橋梁,所有Activity對View的操作都需要借助它。

2)Activity和Window

在Activity啟動過程中,系統會調用attach方法,并建立PhoneWindow執行個體;之後,在具體Activity的onCreate函數中,通常都會調用setContentView方法,進而調用PhoneWindow的setContentView方法設定DecorView。

3)Window和View

Window通過WindowManagerImp類型的成員變量mWindowManager操作View。WindowManagerImpl是WindowManager接口的具體實作,該類通過WindowManagerGlobal完成addView、removeView、updateViewLayout這三個方法。

進一步地,PhoneWindow的成員變量DecorView,可以通過getViewRootImpl方法擷取ViewRoot執行個體。與之對應的ViewRootImpl類,有個IWindowSession類型的成員變量mWindowSession,能夠通路WMS。

小結:PhoneWindow類,是連接配接Activity跟View的橋梁;而ViewRootImpl是 View 和 WindowMangerService之間的橋梁。

(相關完整且成體系的文章,可參見本人原創的開源電子書《Android系統與性能優化》,位址:https://github.com/carylake/androidnotes)

繼續閱讀