您的位置:首页 > 其它

从源码角度分析view的draw过程

2015-08-21 10:36 309 查看
上面两篇文章已经分析了Android view的测量和布局且详细介绍了linearLayout的measure机制。另外特别感谢博乐对我前一篇文章的推荐以,我也会一如既往的写好每一篇文章。

1、从源码角度分析Android View的绘制机制(一)

2、从源码角度分析linearLayout测量过程以及weight机制

3、从源码角度分析view的layout过程

和前面介绍的一样,view的绘制也是从ViewRoot的performTraversals方法中开启。

measure过程
        layout过程
        .......
        if (triggerGlobalLayoutListener) {
            attachInfo.mRecomputeGlobalAttributes = false;
            // 全局布局完成后,进行分发
            attachInfo.mTreeObserver.dispatchOnGlobalLayout();
        }
        ........
        // 在draw之前调用
        boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
        if (!cancelDraw && !newSurface) {
            mFullRedrawNeeded = false;
            // 开始 绘制
            draw(fullRedrawNeeded);
            ..........
                try {
                    // 告诉wms绘制完毕
                    sWindowSession.finishDrawing(mWindow);
                } catch (RemoteException e) {
                }
            }


完成measure和layout过程后,会调用mTreeObserver.dispatchOnGlobalLayout()方法,这个也就可以解决我们平时的一个需求,比如说要在onCreate方法中获取某个控件的测量大小以及它所在屏幕的位置,那么我们就可以给mTreeObserver添加一个OnGlobalLayout监听器,当measure和layout全部完毕后,会自动回调。再比如说你要在系统执行绘制操作前要做些什么事情,同样也可以给mTreeObserver添加一个OnPreDraw监听器,具体使用方式如下:

View view = new View(context);
        ViewTreeObserver mViewTreeObserver = view.getViewTreeObserver();
        mViewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

            }
        });
        mViewTreeObserver.addOnPreDrawListener(new OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                return false;
            }
        });


紧接着就是调用draw方法进行绘制了。

private void draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        if (mUseGL) {
            if (!dirty.isEmpty()) {
                Canvas canvas = mGlCanvas;
                if (mGL != null && canvas != null) {
                    ...........
                    mView.mPrivateFlags |= View.DRAWN;
                    int saveCount = canvas.save(Canvas.MATRIX_S***E_FLAG);
                    try {
                        canvas.translate(0, -yoff);
                        if (mTranslator != null) {
                            mTranslator.translateCanvas(canvas);
                        }
                        // 设置屏幕密度
                        canvas.setScreenDensity(scalingRequired? DisplayMetrics.DENSITY_DEVICE : 0);
                        // 开始绘制
                        mView.draw(canvas);
                        .........
                    } finally {
                        canvas.restoreToCount(saveCount);
                    }
                    ..........
                }
            }
            ............
            return;
        }
        ................
    }


最终调用的还是mView.draw(canvas);draw方法在view中是final的,所以在自定义viewGroup的时候,一般都会重写onDraw方法。view中的draw方法的处理逻辑总共可以分为5个步骤,如下所述:

public void draw(Canvas canvas) {
        final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        // Step 1, draw the background, if needed        绘制背景
        // step 2, If necessary, save the canvas' layers to prepare for fading   
        // Step 3, draw the content                      绘制视图本身
        // Step 4, draw the children                     绘制子视图
        // Step 5,If necessary, draw the fading edges and restore layers   
        // Step 6, draw decorations (scrollbars)         绘制滚动条
}


我们来一步一步的对view中draw方法做如下分析:

// 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);
                }
            }
        }
        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);
            }
            return;
        }
        ...........


我们发现上面的一段代码只是做了绘制步骤中的1、3、4、6,少了步骤2和5,原因是2和5主要是用来绘制一些渐变的,而在if语句中的条件是!verticalEdges && !horizontalEdges,也就是说如果view没有设置渐变框。渐变框可以通过android:fadingEdge设置渐变的方向,android:fadingEdgeLength来设置渐变框的长度。大家可以参照下面的图理解一下:



无论有没有渐变效果,首先第一步就是绘制背景

[b]Step 1:背景的绘制[/b]

background.draw(canvas);调用Drawable的draw方法进行绘制,这个方法其实是个抽象方法,如果说背景是个BitmapDrawable,那么真正的绘制还是调用了native_drawBitmap方法,也就是个native方法。

public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
        if (dst == null) {
            throw new NullPointerException();
        }
        throwIfCannotDraw(bitmap);
        native_drawBitmap(mNativeCanvas, bitmap.ni(), src, dst,
                paint != null ? paint.mNativePaint : 0, mScreenDensity, bitmap.mDensity);
    }


[b]Step 3:绘制视图本身[/b]

调用view的onDraw方法进行视图本身的绘制。onDraw在view中的方法体是个空的,所以自定义view的时候都会重写这个方法。如果本身是个ViewGroup的话,调用的则是viewGroup的onDraw方法,但是viewGroup也没有重写这个方法。以LinearLayout为例,在2.3.3的源码中,其实LinearLayout也没有复写这个方法,貌似到到了3.0之后LinearLayout重写了这个方法,因为LinearLayout中添加了一个新的特性,可以给内部元素添加分隔。

@Override
    protected void onDraw(Canvas canvas) {
        if (mDivider == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawDividersVertical(canvas);
        } else {
            drawDividersHorizontal(canvas);
        }
    }


从源码中也看到,如果mDivider 为null,自己本身的视图就不会绘制。

[b]Step 4:绘制子视图[/b]

开始调用dispatchDraw(canvas)绘制子视图,view中的这个方法也是个空方法体,对于没有孩子的view而言,不需要绘制子视图,所以viewGroup是必须要重写这个方法。

@Override
    protected void dispatchDraw(Canvas canvas) {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    final LayoutParams params = child.getLayoutParams();
                    // 给view设置布局动画
                    attachLayoutAnimationParameters(child, params, i, count);
                    bindLayoutAnimation(child);
                    if (cache) {
                        child.setDrawingCacheEnabled(true);
                        child.buildDrawingCache(true);
                    }
                }
            }

            final LayoutAnimationController controller = mLayoutAnimationController;
            if (controller.willOverlap()) {
                mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
            }

            controller.start();

            mGroupFlags &= ~FLAG_RUN_ANIMATION;
            mGroupFlags &= ~FLAG_ANIMATION_DONE;

            if (cache) {
                mGroupFlags |= FLAG_CHILDREN_DRAWN_WITH_CACHE;
            }

            if (mAnimationListener != null) {
                // 动画开始
                mAnimationListener.onAnimationStart(controller.getAnimation());
            }
        }
        .........
        if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
            for (int i = 0; i < count; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        } else {
            for (int i = 0; i < count; i++) {
                // Override this if you want to change the drawing order of children.
                final View child = children[getChildDrawingOrder(count, i)];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
        }
        // Draw any disappearing views that have animations
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        ...........
   }


绘制子视图的过程中,首先遍历子视图,如果child是visiable的就会给child设置布局动画,这个布局动画我们可以自己定义,如果没有定义的话就会有一个默认的动画。紧跟着就是判断我们是否需要自定义child的绘制顺序。默认是按照index的先后顺序。如果我们想要调整child的绘制顺序可以重写getChildDrawingOrder方法,这个方法默认返回当前childview的index值。最后调用drawChild(canvas, child, drawingTime)方法进行子view的绘制。

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }


[b]Step 6:绘制装饰,比如说滚动条[/b]

调用onDrawScrollBars绘制滚动条
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: