一、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)