Android自定义控件

小试牛刀-onDraw方法

2018-07-10  本文已影响0人  同学别闹

onDraw也有个类似的方法draw方法,还是接着以前的套路,先分析一下draw方法再去具体分析onDraw方法。

draw

  关于draw,源码中有很长一段注释,解释了draw到底做了什么事情。

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *      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)
         */

  简答理解一下draw执行了六个步骤:

  1. 绘制背景
  2. 如果需要,保存canvas的图层信息
  3. 绘制View的内容
  4. 如果有子View,绘制子View
  5. 如果需要,绘制View的边缘等(如阴影)
  6. 绘制 View 的装饰(如滚动)。
   @CallSuper
    public void draw(Canvas canvas) {
        ......
     // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        ......

      // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }
        ......

          if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }
      
      ......

        // 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
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;
        if (drawTop) {
              ......
        }
        if (drawBottom) {
              ......
        }
        if (drawLeft) {
              ......
        }
        if (drawRight) {
              ......
        }
       canvas.restoreToCount(saveCount);

        drawAutofilledHighlight(canvas);

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

        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
      }

  step3:绘制内容的时候调用的View的onDraw(canvas);方法,由于View的内容各不相同所以onDraw(canvas)是一个空方法,需要子类自己去实现。


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

  step4:绘制子View的时候调用dispatchDraw方法,该方法还是一个空方法需要子类自己去实现。因为当只有子View的时候才会去重写,所以看下ViewGroup是怎么实现的。

    /**
     * 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) {

    }

  ViewGroup实现dispatchDraw dispatchDraw(Canvas canvas),由于ViewGroup的dispatchDraw方法内容实现比较多,直接看关键实现

protected void dispatchDraw(Canvas canvas) {
           ......
    for (int i = 0; i < childrenCount; i++) {
            ......
            if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                more |= drawChild(canvas, transientChild, drawingTime);
            }
            ......
    }
}

  遍历了所有的子 View 并调用了 ViewGroup的drawChild 方法,在看下drawChild是怎么实现的

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

  最终发现还是调用的draw方法,因为ViewGroup已经帮我实现好了dispatchDraw(Canvas canvas)方法,所有我们开发中不需要自己去重写,只需要重写onDraw(Canvas canvas)方法即可。

   所以draw是绘制的方法,但是具体如何去绘制子View的内容就需要重写onDraw方法,再看下onDraw方法。

  由于onDraw是一个空方法,具体实现看子类,但是又个关键的参数Canvas,字面意思就是画布,绘制内容光有画布肯定是不行的,还需要画笔,所以有个关于画笔的类Paint,只有画布和画笔结合才能绘制出内容

Paint的几个最常用的方法

Style 效果
Paint.Style.FILL 填充
Paint.Style.FILL_AND_STROKE 描边并填充
Paint.Style.STROKE 描边
//设置透明度,范围为0~255
void  setAlpha(int a)
//是否开启抗锯齿
void  setAntiAlias(boolean aa)
//设置颜色
void  setColor(int color)
//s设置颜色过滤
ColorFilter setColorFilter (ColorFilter filter)
//设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
void setDither(boolean dither)
//设置线条宽度
void setStrokeWidth(float width) 
//设置文字大小
void setTextSize(float textSize)

Canvas的方法都是以draw开头的

先看先Canvas的Api

  Canvas可以绘制弧线(Arc),绘制填充色(ARGB和Color),绘制Bitmap,圆(circle和oval),点(point),线(line),矩形(Rect),图片(Picture),圆角矩形 (RoundRect),文本(text),顶点(Vertices),路径(path)

在了解Canvas之前,先熟悉一下Android下的坐标系

Android中的每个View都有自己的坐标系,不会相互影响。坐标的原点在屏幕的左上角。水平方向是X轴,向右为正,向左为负。垂直方向是Y轴,向下为正,向上为负数。


View坐标系

void drawArc(@NonNull RectF oval, float startAngle, float sweepAngle, boolean useCenter,@NonNull Paint paint)


  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制弧线区域
    RectF rect = new RectF(0, 0, 300, 300);
    canvas.drawArc(rect, //弧线所使用的矩形区域大小
        0,  //开始角度
        300, //扫过的角度
        true, //是否使用中心
        paint);
  }
useCenter == true useCenter == false

  drawArc绘制的区域是起始角度和结束角度连接起来的,其中useCenter为true时圆弧开始角度和结束角度都与中心连接。

drawCircle(float cx, float cy, float radius, @NonNull Paint paint)

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //圆心坐标点X,圆心坐标点Y,半径,画笔
    canvas.drawCircle(100,100,90,paint);
  }
drawCircle

drawColor(@ColorInt int color)

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawColor(Color.RED);
  }
drawColor

绘制区域填充指定颜色,drawRGB(int r, int g, int b)也是同样的

drawBitmap(@NonNull Bitmap bitmap, float left, float top, @Nullable Paint paint)

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher);
  //bitmap对象,bitmap左侧的位置,bitmap顶部的位置,画笔
   canvas.drawBitmap(bitmap,300,300,new Paint());
  }
drawBitmap

drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint)

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制直线
    //起始点X坐标,起始点Y坐标,重点X坐标,终点Y坐标,笔画
    canvas.drawLine(100,100,600,200,paint);
  }
drawLine

drawRect(@NonNull RectF rect, @NonNull Paint paint)

@Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //定义一个矩形区域
    //左,上,右,下
    RectF rect = new RectF(300,300,700,700);
    //绘制矩形
    canvas.drawRect(rect,paint);
  }

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //定义一个矩形区域
    //左,上,右,下
    RectF rect = new RectF(300,300,700,700);
    //绘制带圆角的矩形
    canvas.drawRoundRect(rect,
        100,//x轴的半径
        100,//Y轴的半径
        paint);
  }
drawRect drawRoundRect

drawOval(@NonNull RectF oval, @NonNull Paint paint)

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //定义一个矩形区域
    RectF rect = new RectF(0,0,500,200);
    //绘制内切椭圆
    canvas.drawOval(rect,paint);
  }
drawOval

drawText(@NonNull String text, float x, float y, @NonNull Paint paint)

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    String text = "Android";
    Rect textRect = new Rect();
    paint.getTextBounds(text, 0, text.length(), textRect);
    //获取文字的高度
    float textHeight = textRect.height();
    //文字内容,文字绘制起点X,文字绘制起点Y,画笔
    canvas.drawText(text, 0, 0 + textHeight, paint);
  }

  @Override protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //按照指定坐标 绘制文本内容
    canvas.drawPosText("安卓", new float[]{ 90,90, 200,200 }, paint);
  }
drawText

特别要注意的是文字绘制的起点是从文字的左下角开始的,实际看见文字的Y坐标需要加上文字的自身高度

drawPosText

void drawPath(@NonNull Path path, @NonNull Paint paint)

  drawPath是绘制路径,关于Path可以参考Android开发之Path详解

  • canvas.save() 将已经绘制好的图像保存起来,让后续的操作就该图层上操作
  • canvas.restore()合并图层的操作,作用是将save之后绘制的图像和save之前的图像进行合并
  • canvas.translate()坐标系的平移与翻转,默认绘图坐标原点在屏幕左上角。调用canvas.translate(x,y)之后将从坐标原点(0,0)移动到了(x,y)。之后操作以(x,y)作为原点执行
  • canvas.rotate()将坐标系旋转了一个角度
上一篇下一篇

猜你喜欢

热点阅读