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

上面两篇文章已经分析了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_SAVE_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来设置渐变框的长度。大家可以参照下面的图理解一下:
渐变图

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

Step 1:背景的绘制

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);
    }
Step 3:绘制视图本身

调用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,自己本身的视图就不会绘制。

Step 4:绘制子视图

开始调用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);
    }
Step 6:绘制装饰,比如说滚动条

调用onDrawScrollBars绘制滚动条

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 中,ImageView 是一个常用的控件,通常用于显示图片。其中的 setVisible 方法是用来设置 ImageView 是否可见的方法。下面从源码角度分析一下这个方法的实现。 ImageView 的 setVisible 方法实际上是从 View 类继承而来的,其源码如下: ``` @Override public void setVisible(boolean visible, boolean restart) { super.setVisible(visible, restart); mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY; } ``` 在这个方法中,首先调用了 View 类的 setVisible 方法,而该方法中的实现主要是设置 View 的 visibility 属性。而 ImageView 这个子类中,visibility 属性的设置方法是 setVisibility 方法。因此,如果想要设置 ImageView 的可见性,实际上应该调用的是 setVisibility 方法。 setVisible 方法的第二个参数 restart 表示是否需要重绘 View。在 ImageView 中,如果需要重绘,则会调用 ImageView 的 invalidate 方法,使其重绘。而在 invalidate 方法中,会标记 View 的状态为需要重绘,这个标记的状态就是 mPrivateFlags 变量中的 PFLAG_DRAWABLE_STATE_DIRTY 标记。因此,在 setVisible 方法中,还需要将这个标记清除,以便在下一次需要重绘时,重新标记。 综上所述,ImageView 的 setVisible 方法主要是继承自 View 类,并调用了其父类的 setVisible 方法,同时还需要清除重绘标记。如果想要设置 ImageView 的可见性,应该调用 setVisibility 方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值