LayoutChangeListeners.clone();
? ? ? ? ? ? ? ? int numListeners = listenersCopy.size();
? ? ? ? ? ? ? ? for (int i = 0; i < numListeners; ++i) {
? ? ? ? ? ? ? ? ? ? listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
? ? ? ? mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
? ? }
该方法接收四个参数是子view相对于父view而言的上下左右位置。然而我们发现其中调用到的onLayout方法默认的实现是空的。这是因为确定view在布局的位置这个操作应该由Layout根据自身特点来完成。任何布局的定义都要重写其onLayout方法,并在其中设定子view的位置。
绘制
在进行完测定尺寸和定位之后,终于可以开始绘制了。这里的工作仍是通过递归来完成的。view调用draw方法来进行绘制,里面调用onDraw来绘制自身,如果还有子view则需要调用dispatchDraw来绘制子view。
绘制需要调用draw方法,总共分为六个步骤:
public void draw(Canvas canvas) {
? ? ? ? final int privateFlags = mPrivateFlags;
? ? ? ? final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
? ? ? ? ? ? ? ? (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
? ? ? ? mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
? ? ? ? // Step 1, 绘制背景
? ? ? ? int saveCount;
? ? ? ? if (!dirtyOpaque) {
? ? ? ? ? ? drawBackground(canvas);
? ? ? ? }
? ? ? ? // 如果不需要,跳过步骤2和5
? ? ? ? final int viewFlags = mViewFlags;
? ? ? ? boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
? ? ? ? boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
? ? ? ? if (!verticalEdges && !horizontalEdges) {
? ? ? ? ? ? // Step 3, 绘制内容
? ? ? ? ? ? if (!dirtyOpaque) onDraw(canvas);
? ? ? ? ? ? // Step 4, 绘制子view
? ? ? ? ? ? dispatchDraw(canvas);
? ? ? ? ? ? // Step 6, 绘制装饰部分
? ? ? ? ? ? onDrawScrollBars(canvas);
? ? ? ? ? ? if (mOverlay != null && !mOverlay.isEmpty()) {
? ? ? ? ? ? ? ? mOverlay.getOverlayView().dispatchDraw(canvas);
? ? ? ? ? ? }
? ? ? ? ? ? // 完成
? ? ? ? ? ? return;
? ? ? ? }
? ? }
我们选择常规的绘制过程,不介绍2,5步骤。
第一步,调用drawBackground绘制背景图案:
private void drawBackground(Canvas canvas) {
? ? ? ? final Drawable background = mBackground;
? ? ? ? // 获取到当前view的背景,是一个drawable对象 if (background == null) {
? ? ? ? ? ? return;
? ? ? ? }
? ? ? ? if (mBackgroundSizeChanged) {// 判断背景大小是否变化,是则设置背景边界
? ? ? ? ? ? background.setBounds(0, 0,? mRight - mLeft, mBottom - mTop);
? ? ? ? ? ? mBackgroundSizeChanged = false;
? ? ? ? ? ? mPrivateFlags3 |= PFLAG3_OUTLINE_INVALID;
? ? ? ? }
? ? ? ? // Attempt to use a display list if requested.
? ? ? ? if (canvas.isHardwareAccelerated() && mAttachInfo != null
? ? ? ? ? ? ? ? && mAttachInfo.mHardwareRenderer != null) {
? ? ? ? ? ? mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
? ? ? ? ? ? final RenderNode displayList = mBackgroundRenderNode;
? ? ? ? ? ? if (displayList != null && displayList.isValid()) {
? ? ? ? ? ? ? ? setBackgroundDisplayListProperties(displayList);
? ? ? ? ? ? ? ? ((HardwareCanvas) canvas).drawRenderNode(displayList);
? ? ? ? ? ? ? ? return;
? ? ? ? ? ? }
? ? ? ? }
? ? ? // 调用drawable对象的绘制方法完成绘制
? ? ? ? final int scrollX = mScrollX;
? ? ? ? final int scrollY = mScrollY;
? ? ? ? if ((scrollX | scrollY) == 0) {
? ? ? ? ? ? background.draw(canvas);
? ? ? ? } else {
? ? ? ? ? ? canvas.translate(scrollX, scrollY);
? ? ? ? ? ? background.draw(canvas);
? ? ? ? ? ? canvas.translate(-scrollX, -scrollY);
? ? ? ? }
? ? }
第三步,调用onDraw方法绘制view的内容,由于不同的view内容不同,所以需要子类进行重写。
第四步,绘制子view,这里仍然需要当前layout的dispatchDraw方法来完成对各子view的绘制。
第六步,绘制滚动条。
通常情况下,我们自定义view,复写onDraw方法来绘制我们定义的view的内容即可。
总结
通过研究view类的源码,我们可以发现,在整个view的绘制流程中我们需要完成测定尺寸,布局定位,绘制这三个步骤。Android在设计过程中,将固定不变的流程设计为不可更改的模板方法,然而需要根据不同情况而定的内容则交给开发者来完成重写,在模板方法中调用即可。这样设计即保证了整个流程的完整,又给开发工作带来了灵活。同时,在类中又根据不同情况定义了不同的flag,来满足不同情况的绘制需求,以后有机会再具体研究这些flag的具体意义。