美文网首页Android自定义控件
小试牛刀-onDraw方法

小试牛刀-onDraw方法

作者: 同学别闹 | 来源:发表于2018-07-10 16:32 被阅读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的几个最常用的方法

    • Paint.setStyle(Style style) 设置绘制模式
    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()将坐标系旋转了一个角度

    相关文章

      网友评论

        本文标题:小试牛刀-onDraw方法

        本文链接:https://www.haomeiwen.com/subject/bsgguftx.html