View的draw过程

自定义 view 的最后一步是绘制,使用 Canvas 对象绘制出我们想要的效果。

绘制用到的Canvas

draw 方法中的参数都有个 Canvas 对象,先来了解一下这个东西。

The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

官方文档-Canvas and Drawables

想要绘制一些东西,需要4个基础组件:

  1. 一个Bitmap,用来持有像素
  2. 一个Canvas画布,用来写入bitmap
  3. 一个绘制的图元,如(Rect,Path,text,Bitmap)
  4. 一个画笔,用来描述绘图的颜色和样式

view中的draw方法不需要我们自己创建一个bitmap,系统已经为我们创建好了。

给ImageView设置bitmap

private void drawBitmap(){
    Bitmap bitmap = Bitmap.createBitmap(800, 400, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    canvas.drawColor(Color.GREEN);
    Paint paint = new Paint();
    paint.setColor(Color.RED);
    paint.setTextSize(60);
    canvas.drawText("hello, everyone",150,200,paint);
    mImageView.setImageBitmap(bitmap);
  }
复制代码

在此处为canvas设置一个Bitmap,然后利用canvas画了一小段文字,最后使用ImageView显示了Bitmap。

在onDraw里面绘制不同的图形

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

    // 绘制白色矩形
    mPaint.reset();
    mPaint.setColor(Color.WHITE);
    canvas.drawRect(0, 0, 800, 800, mPaint);

    // 绘制直线
    mPaint.reset();
    mPaint.setColor(Color.RED);
    mPaint.setStrokeWidth(10);
    canvas.drawLine(450, 30, 570, 170, mPaint);

    //-------->绘制带边框的矩形
    mPaint.reset();
    mPaint.setStrokeWidth(10);
    mPaint.setARGB(150, 90, 255, 0);
    mPaint.setStyle(Paint.Style.STROKE);
    @SuppressLint("DrawAllocation") RectF rectF1 = new RectF(30, 60, 350, 350);
    canvas.drawRect(rectF1, mPaint);
    //-------->绘制实心圆
    mPaint.reset();
    mPaint.setStrokeWidth(14);
    mPaint.setColor(Color.GREEN);
    mPaint.setAntiAlias(true);
    canvas.drawCircle(670, 300, 70, mPaint);

    //-------->绘制椭圆
    mPaint.reset();
    mPaint.setColor(Color.YELLOW);
    RectF rectF2 = new RectF(200, 430, 600, 600);
    canvas.drawOval(rectF2, mPaint);

    //-------->绘制文字
    mPaint.reset();
    mPaint.setColor(Color.BLACK);
    mPaint.setTextSize(60);
    mPaint.setUnderlineText(true);
    canvas.drawText("Hello Android", 150, 720, mPaint);
  }
复制代码

对canvas的操作

  1. canvas.translate
  2. canvas.rotate
  3. canvas.clipRect
  4. canvas.save和canvas.restore
  5. PorterDuffXfermode
  6. Bitmap和Matrix
  7. Shader
  8. PathEffect
显示圆角图片
   /**
     * @param bitmap 原图
     * @param pixels 角度
     * @return 带圆角的图
     */
    public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
        int width=bitmap.getWidth();
        int height=bitmap.getHeight();
        Bitmap roundCornerBitmap = Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(roundCornerBitmap);
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setAntiAlias(true);
        Rect rect = new Rect(0, 0, width, height);
        RectF rectF = new RectF(rect);
        canvas.drawRoundRect(rectF, pixels, pixels, paint);
        PorterDuffXfermode xfermode=new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
        paint.setXfermode(xfermode);
        canvas.drawBitmap(bitmap, rect, rect, paint);
        return roundCornerBitmap;
    }
复制代码

draw

view的draw()方法:

有2个draw()方法,分别是:

  1. void draw(Canvas canvas)
  2. boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)

源码中提及到draw有6个过程:

  1. Draw the background
  2. If necessary, save the canvas' layers to prepare for fading(如有必要,保存画布层以准备褪色)
  3. Draw view's content
  4. Draw children
  5. If necessary, draw the fading edges and restore layers(如有必要,绘制褪色边缘并恢复图层)
  6. Draw decorations (scrollbars for instance)(绘制装饰(例如滚动条))

一般情况下可以忽略第2步和第5步。

绘制第2步和第5步要相对耗时一点。

draw(canvas)源码解析

  /**
   * 手动的把当前的view以及它的所有子view渲染到画布上。
   * 在调用此方法之前必须完成完整的layout过程。实现一个view的时候,
   * 实现{@link #onDraw(android.graphics.Canvas)}而不是覆盖此方法。
   * 覆盖这个方法的时候一定要先调用父类super.draw(canvas)方法。
   *
   * @param canvas 渲染视图的画布。
   */
  @CallSuper 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;
    

    // 第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) {
      // 第3步:绘制内容
      if (!dirtyOpaque) onDraw(canvas);

      // 第4步:绘制子view
      dispatchDraw(canvas);

      // Overlay is part of the content and draws beneath Foreground
      if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
      }

      // 第6步:绘制decoration
      onDrawForeground(canvas);

      // 结束
      return;
    }

        /*
         * 下面是少数情况下的完整的流程
         */


    // Step 2, save the canvas' layers
    ....省略第2步的代码

    // Step 3, draw the content
    if (!dirtyOpaque) onDraw(canvas);

    // Step 4, draw the children
    dispatchDraw(canvas);

    // Step 5, draw the fade effect and restore layers
    ....省略第5步的代码

    // Step 6, draw decorations (foreground, scrollbars)
    onDrawForeground(canvas);
  }
复制代码

draw(Canvas canvas, ViewGroup parent, long drawingTime)

ViewGroup.drawChild()调用这个方法来让子view绘制自身,这里View根据图层类型和硬件加速特性来渲染。

onDraw

View 中的 onDraw() 方法:

/**
     * Implement this to do your drawing.
     *
     * @param canvas the canvas on which the background will be drawn
     */
    protected void onDraw(Canvas canvas) {
    }
复制代码

子类实现这个方法来绘制,参数canvas用来绘制背景画布。

dispatchDraw

    /**
     * Called by draw to draw the child views. This may be overridden
     * by derived classes to gain control just before its children are drawn
     * (but after its own view has been drawn).
     * @param canvas the canvas on which to draw the view
     */
    protected void dispatchDraw(Canvas canvas) {

    }
复制代码

绘制自身之后,绘制子view之前可以调用这个方法。

ViewGroup 中的 drawChild()方法

  /**
   * 绘制其中一个子view
   *
   * @param canvas 绘制子view的canvas
   * @param child 子view
   * @param drawingTime draw发生的时间
   * @return 如果是调用invalidate()绘制的话就返回true
   */
  protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
  }
复制代码

draw的顺序图

参考

自定义View系列教程04--Draw源码分析及其实践

How Android Draws Views

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值