Android组件体系之视图绘制

时间:2021-12-01 06:31:55

一、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);
Android组件体系之视图绘制

其中绘制背景的实现,是通过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);     }
Android组件体系之视图绘制

在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; }
Android组件体系之视图绘制


该方法会调用到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)