Android View 绘制流程之三:draw绘制

系列文章:
Android View 绘制流程之一:measure测量
Android View 绘制流程之二:layout布局
Android View 绘制流程之三:draw绘制
Android View 绘制流程之四:绘制流程触发机制

draw()方法是View系统测绘流程的最后一步,就是绘制,当view测量完大小、确定完位置后,就需要在其位置处绘制出其内容等视觉上的东西;View的draw方法有固定的流程,一般ViewGroup需要在dispatchDraw方法中完成对子view的绘制,View需要在onDraw方法里完成对自己内容的绘制。

一.draw方法的整体流程

在这里插入图片描述
ViewRootImpl里调用performDraw()方法,该方法里最终会调用根view的draw方法开始整个view的绘制

View的draw(Canvas canvas)方法主要流程已经规定好:

public void draw(Canvas canvas) {
   
    final int privateFlags = mPrivateFlags;
	final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
        	(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);//如果view的DIRTY标志是OPAQUE,说明其child的该区域不透明,就不用绘制这块的背景和内容了
	mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;//标志DRAWN,已经绘制

    // Step 1, draw the background, if needed
    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, ViewGroup需要重写该方法实现子view的绘制
        dispatchDraw(canvas);

        // ...

        // Step 6, 绘制一些前景,比如前景图、ScrollBar等
        onDrawForeground(canvas);

        // we're done...
        return;
    }

    ...
	//正常的全部流程
    // Step 2, 用canvas.saveLayers的方法保存渐变边的图层
    ...

    // Step 3, 4

    // Step 5, 绘制图层
    ...

    canvas.restoreToCount(saveCount);//恢复canvas状态

    // Step 6
	...
}

流程很清晰,大部分view就是先绘制必要的背景,然后调用onDraw绘制自身内容,绘制完后如果是ViewGroup,还会调用dispatchDraw绘制子view,最后绘制前景一些UI即可;对于一些特殊view,只是多加了绘制渐变框的一步(使用保存图层、绘制图层、恢复画布的做法)

下面就来看看这几个流程的具体实现,这里有一点需要注意,整个view调用draw时的参数,是一个Canvas对象,是ViewRootImpl里创建的SurfaceView的画布,对于整个屏幕来说,canvas的起始点就是0,0,但是对于每个View来说,为了使view在使用canvas绘制时的处理方便,每次在调用view的draw方法前,都会用平移的方法将canvas的其实点移动到view的起始点,这样对于view自己来说,canvas总是从"原点"开始的,所以只需照常绘制即可;那么这点是怎么实现的,下面的各个流程会解释到:

二.drawBackground实现

private void drawBackground(Canvas canvas) {
   
    final Drawable background = mBackground;
    if (background == null) {
   
        return;
    }

    setBackgroundBounds();//设置drawable边界

    // 硬件加速绘制
    ...

    final int scrollX = mScrollX;
    final int scrollY = mScrollY;
    if ((scrollX | scrollY) == 0) {
   
        background.draw(canvas);
    } else {
   //如果有滚动偏移量,则要将canvas原点移动,然后交由drawable绘制,还要再将原点移动回来
        canvas.translate(scrollX, scrollY);
        background.draw(canvas);
        canvas.translate(-scrollX, -scrollY);
    }
}

由代码可知,如果有view有偏移量,比如向上滚动了10px,则mScrollY=10(即新的内容原点就是原来的y=10处),那么传递给view的canvas的起始点就是(0,-10),而background是针对于view的,不是view内部内容的,不应该跟随着内容的滚动而滚动,所以canvas需要移动(0,10)到(0,0),再去绘制背景drawable,之后再复原到(0,-10),用于view内容的绘制;且无论是否硬件加速绘制,都会有相同的处理
在这里插入图片描述

三.onDraw

onDraw(canvas)方法是view绘制自己内容的方法,也是我们自定义view最常用的方法,我们可以使用canvas绘制任意元素,而不需要考虑滚动,因为传递过来的canvas对于view来说就是从(0,0)开始的,下面拿ImageView来举例:

protected void onDraw(Canvas canvas) {
   
    super.onDraw(canvas);

    ...

    if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
   
        mDrawable.draw(canvas);//直接绘制在canvas上即可
    } else {
   
        int saveCount = canvas.getSaveCount();//保存画布状态
        canvas.save();
        
        ...
        
        canvas.translate(mPaddingLeft, mPaddingTop);//平移

        if (mDrawMatrix != null) {
   //矩阵变换
            canvas.concat(mDrawMatrix);
        }
        mDrawable.draw(canvas);//绘制在canvas上
        canvas.restoreToCount(saveCount);//恢复画布状态
    }
}

有代码可知,onDraw里基本就是使用canvas绘制一些元素即可,其canvas自身也提供了许多绘制元素方法,这里不在赘述。

四.dispatchDraw的实现

在这里插入图片描述在这里插入图片描述
dispatchDraw(canvas)方法是ViewGroup需要重写,进行子view绘制的方法,上面已经说过view绘制流程,那么可得知该方法是在自身onDraw方法调用后执行,也就是说view先绘制自身内容,再绘制子view而ViewGroup其实已经实现了dispatchDraw方法,而大部分ViewGroup绘制子view的流程都是如此,所以大部分的ViewGroup子类直接调用该方法即可,无需再处理,下面就来看看这个方法:

protected void dispatchDraw(Canvas canvas) {
   
    boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;
	//如果ViewGroup有布局动画
    if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
   
        final boolean buildCache = !isHardwareAccelerated();
        for (int i = 0; i < childrenCount; i++) {
   
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
   
                final LayoutParams params = child.getLayoutParams();
                attachLayoutAnimationParameters(child,
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值