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

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章