view 的onDraw过程源码分析及应用

上一节我们讲了view 的摆放过程,接着上节讲讲onDraw view的绘制过程,这个方法在平时开发过程中使用的几率还是比较高的,有必要深入的了解下。

讲解顺序:

1.控件绘制原理分析

2.onDraw 中绘制基本图形及文字

3.绘制路径,橡皮及自定义按钮点击事件

 

1.控件绘制原理分析

onMeasure 用于view 的测量,包括测量模式和测量值,具体就是根据父类和自身的属性值来确定测量模式和自身显示的大小。

 onLayout  用于view 位置的摆放,onMeasure 知道了view的大小,onLayout就是根据父view 和自身view的设置属性决定摆放在父view的那个位置。

onDrow 用于view 的绘制,通过上面两部知道了view的大小及摆放位置,那么ondrow 就是将view 绘制出来。

上面两个方法就不多说了,前边讲过,就看下下ImageView 的onDrow 方法

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

        if (mDrawable == null) {
            return; // couldn't resolve the URI
        }

        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
            return;     // nothing to draw (empty bounds)
        }

        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
            mDrawable.draw(canvas);
        } else {
            final int saveCount = canvas.getSaveCount();
            canvas.save();

            if (mCropToPadding) {
                final int scrollX = mScrollX;
                final int scrollY = mScrollY;
                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                        scrollX + mRight - mLeft - mPaddingRight,
                        scrollY + mBottom - mTop - mPaddingBottom);
            }

            canvas.translate(mPaddingLeft, mPaddingTop);

            if (mDrawMatrix != null) {
                canvas.concat(mDrawMatrix);
            }
            mDrawable.draw(canvas);
            canvas.restoreToCount(saveCount);
        }
    }

其实和简单,就是通过画布将图片绘制到画布上,mDrawable 就是我们设置的资源,

mDrawable.draw(canvas);通过这个方法将图片绘制到画布上。由于draw它是一个抽象方法,所以要看下那里实现的。
public abstract void draw(@NonNull Canvas canvas);

那么它是在哪里实现的呢,看了下,实现的地方比较多,所以看其中一个,BitmapDrawable 类,BitmapDrawable 继承自 Drawable 实现了draw 方法

  @Override
    public void draw(Canvas canvas) {
        final Bitmap bitmap = mBitmapState.mBitmap;
        if (bitmap == null) {
            return;
        }

        final BitmapState state = mBitmapState;
        final Paint paint = state.mPaint;
        if (state.mRebuildShader) {
            final Shader.TileMode tmx = state.mTileModeX;
            final Shader.TileMode tmy = state.mTileModeY;
            if (tmx == null && tmy == null) {
                paint.setShader(null);
            } else {
                paint.setShader(new BitmapShader(bitmap,
                        tmx == null ? Shader.TileMode.CLAMP : tmx,
                        tmy == null ? Shader.TileMode.CLAMP : tmy));
            }

            state.mRebuildShader = false;
        }

        final int restoreAlpha;
        if (state.mBaseAlpha != 1.0f) {
            final Paint p = getPaint();
            restoreAlpha = p.getAlpha();
            p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
        } else {
            restoreAlpha = -1;
        }

        final boolean clearColorFilter;
        if (mTintFilter != null && paint.getColorFilter() == null) {
            paint.setColorFilter(mTintFilter);
            clearColorFilter = true;
        } else {
            clearColorFilter = false;
        }

        updateDstRectAndInsetsIfDirty();
        final Shader shader = paint.getShader();
        final boolean needMirroring = needMirroring();
        if (shader == null) {
            if (needMirroring) {
                canvas.save();
                // Mirror the bitmap
                canvas.translate(mDstRect.right - mDstRect.left, 0);
                canvas.scale(-1.0f, 1.0f);
            }

            canvas.drawBitmap(bitmap, null, mDstRect, paint);

            if (needMirroring) {
                canvas.restore();
            }
        } else {
            updateShaderMatrix(bitmap, paint, shader, needMirroring);
            canvas.drawRect(mDstRect, paint);
        }

        if (clearColorFilter) {
            paint.setColorFilter(null);
        }

        if (restoreAlpha >= 0) {
            paint.setAlpha(restoreAlpha);
        }
    }

最终的绘制还是通过canvas.drawBitmap(bitmap, null, mDstRect, paint); 来绘制出来的

看下ImageView 中 mDrawable是如何被赋值的。方法较多我们只看一个

  public void setImageBitmap(Bitmap bm) {
        // Hacky fix to force setImageDrawable to do a full setImageDrawable
        // instead of doing an object reference comparison
        mDrawable = null;
        if (mRecycleableBitmapDrawable == null) {
            mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
        } else {
            mRecycleableBitmapDrawable.setBitmap(bm);
        }
        setImageDrawable(mRecycleableBitmapDrawable);
    }
 
通过setImageBitmap 最终调用到 updateDrawable(Drawable d) 方法
private void updateDrawable(Drawable d) {
        if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
            mRecycleableBitmapDrawable.setBitmap(null);
        }

        boolean sameDrawable = false;

        if (mDrawable != null) {
            sameDrawable = mDrawable == d;
            mDrawable.setCallback(null);
            unscheduleDrawable(mDrawable);
            if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
                mDrawable.setVisible(false, false);
            }
        }

        mDrawable = d;

        if (d != null) {
            d.setCallback(this);
            d.setLayoutDirection(getLayoutDirection());
            if (d.isStateful()) {
                d.setState(getDrawableState());
            }
            if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
                final boolean visible = sCompatDrawableVisibilityDispatch
                        ? getVisibility() == VISIBLE
                        : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
                d.setVisible(visible, true);
            }
            d.setLevel(mLevel);
            mDrawableWidth = d.getIntrinsicWidth();
            mDrawableHeight = d.getIntrinsicHeight();
            applyImageTint();
            applyColorMod();

            configureBounds();
        } else {
            mDrawableWidth = mDrawableHeight = -1;
        }
    }

 

在这里看到 mDrawable 被赋值了,其实就是 setImageDrawable(mRecycleableBitmapDrawable);的参数

BitmapDrawable 类对象。到这里ImageView 的基本绘制就讲完了,其实本质上还是使用了画布来绘制。

2.onDraw 中绘制基本图形及文字

主要是绘制一些常用的图形,及文字的绘制,并且通过矩阵变换方式实现背景图的平移和缩放

先看下整体的效果图

效果1

先看下图形绘制:

 /**
     * 绘制一部分图形
     *
     * @param canvas
     */
    private void drawGraphics(Canvas canvas) {
        isFirst = false;
        Paint paint = new Paint();
      
        paint.setAntiAlias(true);//设置抗锯齿
        paint.setColor(Color.RED);//画笔颜色
        paint.setStyle(Paint.Style.STROKE);//空心画笔
        paint.setStrokeWidth(3); //设置笔刷的粗细
        canvas.drawCircle(40, 40, 30, paint);
        canvas.drawRect(10, 90, 70, 150, paint);
        canvas.drawRect(10, 170, 70, 200, paint);
        canvas.drawOval(new RectF(10, 220, 70, 250), paint);//椭圆
        /**
         * 使用path 画三角形
         */
        Path path = new Path();
        path.moveTo(10, 330);
        path.lineTo(40, 270);
        path.lineTo(70, 330);
        path.close();
        canvas.drawPath(path, paint);
        /**
         * 梯形
         */
        Path path1 = new Path();
        path1.moveTo(10, 410);
        path1.lineTo(25, 350);
        path1.lineTo(55, 350);
        path1.lineTo(70, 410);
        path1.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
        /*
         * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
         * 如果不设置close,图形不封闭。
         *
         * 当然,你也可以不设置close,再添加一条线,效果一样。
         */
        canvas.drawPath(path1, paint);

        /**
         * 第二列
         */
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL); //实心
        canvas.drawCircle(80 + 40, 40, 30, paint);
        canvas.drawRect(80 + 10, 90, 80 + 70, 150, paint);
        canvas.drawRect(80 + 10, 170, 80 + 70, 200, paint);
        canvas.drawOval(new RectF(80 + 10, 220, 80 + 70, 250), paint);//椭圆
        /**
         * 使用path 画三角形
         */
        Path path2 = new Path();
        path2.moveTo(80 + 10, 330);
        path2.lineTo(80 + 40, 270);
        path2.lineTo(80 + 70, 330);
        path2.close();
        canvas.drawPath(path2, paint);
        /**
         * 梯形
         */
        Path path3 = new Path();
        path3.moveTo(80 + 10, 410);
        path3.lineTo(80 + 25, 350);
        path3.lineTo(80 + 55, 350);
        path3.lineTo(80 + 70, 410);
        path3.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
        /*
         * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
         * 如果不设置close,图形不封闭。
         *
         * 当然,你也可以不设置close,再添加一条线,效果一样。
         */
        canvas.drawPath(path3, paint);
        /**
         * 第三列
         */
        /*
         * LinearGradient shader = new LinearGradient(0, 0, endX, endY, new
         * int[]{startColor, midleColor, endColor},new float[]{0 , 0.5f,
         * 1.0f}, TileMode.MIRROR);
         * 参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点
         * 其中参数new int[]{startColor, midleColor,endColor}是参与渐变效果的颜色集合,
         * 其中参数new float[]{0 , 0.5f, 1.0f}是定义每个颜色处于的渐变相对位置, 这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布
         */
        Shader mShader = new LinearGradient(0, 0, 100, 410, new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}, null, Shader.TileMode.REPEAT);
        // Shader.TileMode三种模式
        // REPEAT:沿着渐变方向循环重复
        // CLAMP:如果在预先定义的范围外画的话,就重复边界的颜色
        // MIRROR:与REPEAT一样都是循环重复,但这个会对称重复
        paint.setShader(mShader);

        canvas.drawCircle(160 + 40, 40, 30, paint);
        canvas.drawRect(160 + 10, 90, 160 + 70, 150, paint);
        canvas.drawRect(160 + 10, 170, 160 + 70, 200, paint);
        canvas.drawOval(new RectF(160 + 10, 220, 160 + 70, 250), paint);//椭圆
        /**
         * 使用path 画三角形
         */
        Path path4 = new Path();
        path4.moveTo(160 + 10, 330);
        path4.lineTo(160 + 40, 270);
        path4.lineTo(160 + 70, 330);
        path4.close();
        canvas.drawPath(path4, paint);
        /**
         * 梯形
         */
        Path path5 = new Path();
        path5.moveTo(160 + 10, 410);
        path5.lineTo(160 + 25, 350);
        path5.lineTo(160 + 55, 350);
        path5.lineTo(160 + 70, 410);
        path5.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
        /*
         * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
         * 如果不设置close,图形不封闭。
         *
         * 当然,你也可以不设置close,再添加一条线,效果一样。
         */
        canvas.drawPath(path5, paint);
        /**
         * 绘制文字
         * 第四列
         */
        paint.setTextSize(24);
        canvas.drawText("圆形", 240, 50, paint);
        canvas.drawText("正方形", 240, 120, paint);
        canvas.drawText("长方形", 240, 190, paint);
        canvas.drawText("椭圆形", 240, 250, paint);
        canvas.drawText("三角形", 240, 320, paint);
        canvas.drawText("梯形", 240, 390, paint);
    }

这些就是绘制一些基本的图形,在一些自定义的控件中比较常用,下面讲下图形的简单变换

基本的常用矩阵变换操作包括平移、缩放、旋转、斜切。

在Android中,用Matrix这个类代表矩阵。Matrix是一个3x3的矩阵

Matrix提供了基本的变换,translate、scale、rotate、skew,针对每种变换,Android提供了set、pre和post三种操作方式。

    set用于设置单位矩阵中的值。我们通过new Matrix()得到的是一个单位矩阵,后续的矩阵变换都是针对这个单位矩阵进行变换。如Matrix.setRotate(90)、Matrix.setTranslate(10,20)等。
    pre指先乘,相当于矩阵运算中的右乘。如Matrix.setRotate(90),表示M' = M * R(90)。
    post指后乘,相当于矩阵运算中的左乘,如Matrix.setRotate(90),表示M' = R(90) * M。

在图形处理时,矩阵的运算是从右边往左边方向进行运算的。这就形成了越在右边(右乘)的矩阵,越先运算(先乘),反之亦然。所以,右乘就是先乘,左乘就是后乘。更多了解请参考  浅谈矩阵变换——Matrix

现在我们需要将左上角的图片显示在整个view的中央,并且宽高撑满。

 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
        float scoleW = width * 1.0f / bitmap.getWidth();
        float scoleH = height * 1.0f / bitmap.getHeight();
       // float scale = Math.max(scoleW, scoleH);
        matrix = new Matrix();
        /**
         * 先乘 平移
         */
        matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
        /**
         * 后乘 缩放
         */
        matrix.postScale(scoleW, scoleH, width / 2, height / 2);


//        matrix.setRotate(20);
//        matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
//        matrix.postRotate(-20);


        canvas.drawBitmap(bitmap, matrix, brushPaint);
  brushPaint = new Paint();
        brushPaint.setAntiAlias(true);
        brushPaint.setStyle(Paint.Style.STROKE);
        brushPaint.setStrokeCap(Paint.Cap.ROUND);
        brushPaint.setStrokeJoin(Paint.Join.ROUND);
        brushPaint.setColor(Color.RED);
        brushPaint.setStrokeWidth(4);

3.绘制路径,橡皮及自定义按钮点击事件

1.自定义按钮及事件

 基本思路就是通过画布绘制一个图形,并且在图形内部绘制文字,通过触摸事件获取的位置,与当前图形的矩形区域对比,判断是否在其区域内部。从而判断是否需要相应点击事件,现在就来绘制橡皮按钮和画笔按钮。

       /**
     * 画笔
     */
    Paint brushPaint;
    /**
     * 橡皮
     */
    Paint rubberPaint;

 /**
         * 画笔
         */
        brushPaint = new Paint();
        brushPaint.setAntiAlias(true);
        brushPaint.setStyle(Paint.Style.STROKE);
        brushPaint.setStrokeCap(Paint.Cap.ROUND);
        brushPaint.setStrokeJoin(Paint.Join.ROUND);
        brushPaint.setColor(Color.RED);
        brushPaint.setStrokeWidth(4);

        /**
         * 橡皮
         */
        rubberPaint = new Paint();
        //下面这句代码是橡皮擦设置的重点
        rubberPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        rubberPaint.setStrokeWidth(20);
        rubberPaint.setAntiAlias(true);
        rubberPaint.setColor(Color.TRANSPARENT);
        rubberPaint.setStyle(Paint.Style.STROKE);
        rubberPaint.setStrokeJoin(Paint.Join.ROUND);
        rubberPaint.setAlpha(0);

这里定义了两只画笔其中橡皮中setXfermode  属性很重要。

开始绘制按钮 ,在右上角垂直方向绘制橡皮和画笔

 /**
     * 绘制带点击事件的按钮
     *
     * @param canvas
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void drawButton(Canvas canvas) {
        targetRect = new RectF(width - 170, 10, width - 20, 100);
        Paint paint = new Paint();
        paint.setAntiAlias(true);//设置抗锯齿
        paint.setColor(Color.BLUE);//画笔颜色
        paint.setStyle(Paint.Style.FILL);//空心画笔
        paint.setStrokeWidth(3); //设置笔刷的粗细
        canvas.drawRoundRect(targetRect, 20, 20, paint);

        TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setAntiAlias(true);//设置抗锯齿
        textPaint.setColor(Color.WHITE);//画笔颜色
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setStrokeWidth(2); //设置笔刷的粗细
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setTextSize(28);
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float y = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        canvas.drawText("橡皮", targetRect.centerX(), y, textPaint);

        targetRect1 = new RectF(targetRect.left, 110, targetRect.right, 200);
        canvas.drawRoundRect(targetRect1, 20, 20, paint);
        float yp = (targetRect1.bottom + targetRect1.top - fontMetrics.bottom - fontMetrics.top) / 2;
        canvas.drawText("画笔", targetRect.centerX(), yp, textPaint);
    }

通过

canvas.drawRoundRect方法绘制矩形  
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
        drawRoundRect(rect.left, rect.top, rect.right, rect.bottom, rx, ry, paint);
    }

 

rect:指绘制的矩形的绘制区域。

rx:x方向上的圆角半径。

ry:y方向上的圆角半径。

paint:绘制时所使用的画笔。

点击事件处理

 private void buttonOnclick(float x, float y) {
        if (!isChangePaint) {
            return;
        }
        if (pointInRect(x, y, targetRect1)) {
            paintType = PaintType.BRUSH.name();
            Toast.makeText(context, "画笔", Toast.LENGTH_SHORT).show();
        } else if (pointInRect(x, y, targetRect)) {
            paintType = PaintType.RUBBER.name();
            Toast.makeText(context, "橡皮", Toast.LENGTH_SHORT).show();
        }
    }
private boolean pointInRect(float x, float y, RectF rectF) {
        if ((x > rectF.left && x < rectF.right) && (y > rectF.top && y < rectF.bottom)) {
            return true;
        }
        return false;
    }

这个方法用于判断点击事件是否在按钮的区域内。

2.绘制路径及橡皮

这里牵扯到了图层的概念,我们将其分了两个图层,以用于绘制基本图形,及按钮,另一个用于绘制路径及橡皮。

这里就不再多说了直接将所有代码列出来,

public class MyDrawView extends View {
    boolean isFirst = true;
    /**
     * 画笔
     */
    Paint brushPaint;
    /**
     * 橡皮
     */
    Paint rubberPaint;
    Path path;
    private float downX, downY;
    int width, height;
    private RectF targetRect, targetRect1;
    private boolean isChangePaint = false;
    String paintType;
    Context context;
    Canvas mCanvas;
    Bitmap mBitmap;
    private float mX, mY;
    private static final float TOUCH_TOLERANCE = 4;
    private Matrix matrix;

    public enum PaintType {
        /**
         * 橡皮
         */
        RUBBER,
        /**
         * 画笔
         */
        BRUSH
    }

    public MyDrawView(Context context) {
        super(context);
    }

    private boolean pointInRect(float x, float y, RectF rectF) {
        if ((x > rectF.left && x < rectF.right) && (y > rectF.top && y < rectF.bottom)) {
            return true;
        }
        return false;
    }

    public MyDrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setFocusable(true);
        this.context = context;
        paintType = PaintType.BRUSH.name();
        width = getResources().getDisplayMetrics().widthPixels;
        height = getResources().getDisplayMetrics().heightPixels;
        initPaint();

    }

    private void initPaint() {
        path = new Path();
        /**
         * 画笔
         */
        brushPaint = new Paint();
        brushPaint.setAntiAlias(true);
        brushPaint.setStyle(Paint.Style.STROKE);
        brushPaint.setStrokeCap(Paint.Cap.ROUND);
        brushPaint.setStrokeJoin(Paint.Join.ROUND);
        brushPaint.setColor(Color.RED);
        brushPaint.setStrokeWidth(4);

        /**
         * 橡皮
         */
        rubberPaint = new Paint();
        //下面这句代码是橡皮擦设置的重点
        rubberPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
        rubberPaint.setStrokeWidth(20);
        rubberPaint.setAntiAlias(true);
        rubberPaint.setColor(Color.TRANSPARENT);
        rubberPaint.setStyle(Paint.Style.STROKE);
        rubberPaint.setStrokeJoin(Paint.Join.ROUND);
        rubberPaint.setAlpha(0);


        mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        BitmapFactory.Options opts = new BitmapFactory.Options();
        opts.outHeight = height;
        opts.outWidth = width;
        mCanvas = new Canvas(mBitmap);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawGraphics(canvas);
        drawButton(canvas);
        if (mBitmap != null) {
               canvas.drawBitmap(mBitmap, 0, 0, brushPaint);
        }
        if (path != null) {
            if (paintType.equals(PaintType.BRUSH.name())) {
                mCanvas.drawPath(path, brushPaint);
            }

            if (paintType.equals(PaintType.RUBBER.name())) {
                mCanvas.drawPath(path, rubberPaint);
            }
        }

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = event.getX();
                downY = event.getY();
                touch_start(x, y);
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                buttonOnclick(event.getX(), event.getY());
                touch_up();
                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = event.getX();
                float moveY = event.getY();
                if (Math.abs(moveX - downX) > 10f || Math.abs(moveY - downY) > 10f) {
                    // path.lineTo(moveX, moveY);
                    isChangePaint = true;
                } else {
                    isChangePaint = false;
                }
                touch_move(x, y);
                invalidate();
                break;
        }
        return true;
    }

    private void touch_start(float x, float y) {
        path.reset();
        path.moveTo(x, y);
        mX = x;
        mY = y;
    }

    private void touch_move(float x, float y) {
        float dx = Math.abs(x - mX);
        float dy = Math.abs(y - mY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
            mX = x;
            mY = y;
        }
    }


    private void touch_up() {
        path.lineTo(mX, mY);
    }

    private void buttonOnclick(float x, float y) {
        if (!isChangePaint) {
            return;
        }
        if (pointInRect(x, y, targetRect1)) {
            paintType = PaintType.BRUSH.name();
            Toast.makeText(context, "画笔", Toast.LENGTH_SHORT).show();
        } else if (pointInRect(x, y, targetRect)) {
            paintType = PaintType.RUBBER.name();
            Toast.makeText(context, "橡皮", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 绘制一部分图形
     *
     * @param canvas
     */
    private void drawGraphics(Canvas canvas) {
        isFirst = false;
        Paint paint = new Paint();
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
        float scoleW = width * 1.0f / bitmap.getWidth();
        float scoleH = height * 1.0f / bitmap.getHeight();
       // float scale = Math.max(scoleW, scoleH);
        matrix = new Matrix();
        /**
         * 先乘 平移
         */
        matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
        /**
         * 后乘 缩放
         */
        matrix.postScale(scoleW, scoleH, width / 2, height / 2);


//        matrix.setRotate(20);
//        matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
//        matrix.postRotate(-20);


        canvas.drawBitmap(bitmap, matrix, brushPaint);

        paint.setAntiAlias(true);//设置抗锯齿
        paint.setColor(Color.RED);//画笔颜色
        paint.setStyle(Paint.Style.STROKE);//空心画笔
        paint.setStrokeWidth(3); //设置笔刷的粗细
        canvas.drawCircle(40, 40, 30, paint);
        canvas.drawRect(10, 90, 70, 150, paint);
        canvas.drawRect(10, 170, 70, 200, paint);
        canvas.drawOval(new RectF(10, 220, 70, 250), paint);//椭圆
        /**
         * 使用path 画三角形
         */
        Path path = new Path();
        path.moveTo(10, 330);
        path.lineTo(40, 270);
        path.lineTo(70, 330);
        path.close();
        canvas.drawPath(path, paint);
        /**
         * 梯形
         */
        Path path1 = new Path();
        path1.moveTo(10, 410);
        path1.lineTo(25, 350);
        path1.lineTo(55, 350);
        path1.lineTo(70, 410);
        path1.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
        /*
         * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
         * 如果不设置close,图形不封闭。
         *
         * 当然,你也可以不设置close,再添加一条线,效果一样。
         */
        canvas.drawPath(path1, paint);

        /**
         * 第二列
         */
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL); //实心
        canvas.drawCircle(80 + 40, 40, 30, paint);
        canvas.drawRect(80 + 10, 90, 80 + 70, 150, paint);
        canvas.drawRect(80 + 10, 170, 80 + 70, 200, paint);
        canvas.drawOval(new RectF(80 + 10, 220, 80 + 70, 250), paint);//椭圆
        /**
         * 使用path 画三角形
         */
        Path path2 = new Path();
        path2.moveTo(80 + 10, 330);
        path2.lineTo(80 + 40, 270);
        path2.lineTo(80 + 70, 330);
        path2.close();
        canvas.drawPath(path2, paint);
        /**
         * 梯形
         */
        Path path3 = new Path();
        path3.moveTo(80 + 10, 410);
        path3.lineTo(80 + 25, 350);
        path3.lineTo(80 + 55, 350);
        path3.lineTo(80 + 70, 410);
        path3.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
        /*
         * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
         * 如果不设置close,图形不封闭。
         *
         * 当然,你也可以不设置close,再添加一条线,效果一样。
         */
        canvas.drawPath(path3, paint);
        /**
         * 第三列
         */
        /*
         * LinearGradient shader = new LinearGradient(0, 0, endX, endY, new
         * int[]{startColor, midleColor, endColor},new float[]{0 , 0.5f,
         * 1.0f}, TileMode.MIRROR);
         * 参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点
         * 其中参数new int[]{startColor, midleColor,endColor}是参与渐变效果的颜色集合,
         * 其中参数new float[]{0 , 0.5f, 1.0f}是定义每个颜色处于的渐变相对位置, 这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布
         */
        Shader mShader = new LinearGradient(0, 0, 100, 410, new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}, null, Shader.TileMode.REPEAT);
        // Shader.TileMode三种模式
        // REPEAT:沿着渐变方向循环重复
        // CLAMP:如果在预先定义的范围外画的话,就重复边界的颜色
        // MIRROR:与REPEAT一样都是循环重复,但这个会对称重复
        paint.setShader(mShader);

        canvas.drawCircle(160 + 40, 40, 30, paint);
        canvas.drawRect(160 + 10, 90, 160 + 70, 150, paint);
        canvas.drawRect(160 + 10, 170, 160 + 70, 200, paint);
        canvas.drawOval(new RectF(160 + 10, 220, 160 + 70, 250), paint);//椭圆
        /**
         * 使用path 画三角形
         */
        Path path4 = new Path();
        path4.moveTo(160 + 10, 330);
        path4.lineTo(160 + 40, 270);
        path4.lineTo(160 + 70, 330);
        path4.close();
        canvas.drawPath(path4, paint);
        /**
         * 梯形
         */
        Path path5 = new Path();
        path5.moveTo(160 + 10, 410);
        path5.lineTo(160 + 25, 350);
        path5.lineTo(160 + 55, 350);
        path5.lineTo(160 + 70, 410);
        path5.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
        /*
         * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
         * 如果不设置close,图形不封闭。
         *
         * 当然,你也可以不设置close,再添加一条线,效果一样。
         */
        canvas.drawPath(path5, paint);
        /**
         * 绘制文字
         * 第四列
         */
        paint.setTextSize(24);
        canvas.drawText("圆形", 240, 50, paint);
        canvas.drawText("正方形", 240, 120, paint);
        canvas.drawText("长方形", 240, 190, paint);
        canvas.drawText("椭圆形", 240, 250, paint);
        canvas.drawText("三角形", 240, 320, paint);
        canvas.drawText("梯形", 240, 390, paint);
    }

    /**
     * 绘制带点击事件的按钮
     *
     * @param canvas
     */
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void drawButton(Canvas canvas) {
        targetRect = new RectF(width - 170, 10, width - 20, 100);
        Paint paint = new Paint();
        paint.setAntiAlias(true);//设置抗锯齿
        paint.setColor(Color.BLUE);//画笔颜色
        paint.setStyle(Paint.Style.FILL);//空心画笔
        paint.setStrokeWidth(3); //设置笔刷的粗细
        canvas.drawRoundRect(targetRect, 10, 20, paint);

        TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
        textPaint.setAntiAlias(true);//设置抗锯齿
        textPaint.setColor(Color.WHITE);//画笔颜色
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setStrokeWidth(2); //设置笔刷的粗细
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setTextSize(28);
        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float y = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        canvas.drawText("橡皮", targetRect.centerX(), y, textPaint);

        targetRect1 = new RectF(targetRect.left, 110, targetRect.right, 200);
        canvas.drawRoundRect(targetRect1, 20, 20, paint);
        float yp = (targetRect1.bottom + targetRect1.top - fontMetrics.bottom - fontMetrics.top) / 2;
        canvas.drawText("画笔", targetRect.centerX(), yp, textPaint);
    }
}

这里就是所有的代码,如果看后有什么疑问或者问题欢迎留言

参考:

https://www.cnblogs.com/yishujun/p/5559917.html

https://blog.csdn.net/u012964944/article/details/77824768

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android View 是 Android 中最基本的 UI 构建块之一,负责在屏幕上绘制视图并响应用户的操作。下面是一个简单的 View 源码分析过程: 1. 首先,我们需要了解 View 的继承关系。View 是 Android 中所有 UI 组件的基类,它的直接子类包括 ViewGroup、TextView、ImageView 等。其中,ViewGroup 又是各种布局容器的基类,例如 LinearLayout、RelativeLayout 等。 2. 接着,我们可以查看 View 的基本属性。这些属性包括 layout_width、layout_height、padding、background 等。其中,layout_width 和 layout_height 决定了 View 在布局中的大小,padding 指定了 View 的内边距,background 则是 View 的背景。 3. View 的绘制过程可以分为两个阶段:测量和绘制。在测量阶段,View 会根据其 layout_width 和 layout_height 等属性计算出自身的尺寸。在绘制阶段,View 会将自身绘制到屏幕上。 4. View 的事件响应机制是 Android 中 UI 开发的重要部分。当用户触摸屏幕时,系统会将事件传递给 ViewView 会根据自身的点击区域判断是否响应该事件,并将事件传递给其父容器或下一个 View 进行处理。 5. 最后,我们可以查看 View源码实现,深入了解 View 的内部实现逻辑。例如,View 的测量和绘制过程是通过 onMeasure 和 onDraw 方法实现的,事件响应机制是通过 onTouchEvent 和 dispatchTouchEvent 方法实现的。 总的来说,理解 Android View源码实现可以帮助我们更好地理解 Android UI 开发的工作原理,从而编写出更高效、更灵活、更具交互性的应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值