当前位置:   article > 正文

view 的onDraw过程源码分析及应用_view 的 ondraw()

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 方法

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. if (mDrawable == null) {
  5. return; // couldn't resolve the URI
  6. }
  7. if (mDrawableWidth == 0 || mDrawableHeight == 0) {
  8. return; // nothing to draw (empty bounds)
  9. }
  10. if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
  11. mDrawable.draw(canvas);
  12. } else {
  13. final int saveCount = canvas.getSaveCount();
  14. canvas.save();
  15. if (mCropToPadding) {
  16. final int scrollX = mScrollX;
  17. final int scrollY = mScrollY;
  18. canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
  19. scrollX + mRight - mLeft - mPaddingRight,
  20. scrollY + mBottom - mTop - mPaddingBottom);
  21. }
  22. canvas.translate(mPaddingLeft, mPaddingTop);
  23. if (mDrawMatrix != null) {
  24. canvas.concat(mDrawMatrix);
  25. }
  26. mDrawable.draw(canvas);
  27. canvas.restoreToCount(saveCount);
  28. }
  29. }

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

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

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

  1. @Override
  2. public void draw(Canvas canvas) {
  3. final Bitmap bitmap = mBitmapState.mBitmap;
  4. if (bitmap == null) {
  5. return;
  6. }
  7. final BitmapState state = mBitmapState;
  8. final Paint paint = state.mPaint;
  9. if (state.mRebuildShader) {
  10. final Shader.TileMode tmx = state.mTileModeX;
  11. final Shader.TileMode tmy = state.mTileModeY;
  12. if (tmx == null && tmy == null) {
  13. paint.setShader(null);
  14. } else {
  15. paint.setShader(new BitmapShader(bitmap,
  16. tmx == null ? Shader.TileMode.CLAMP : tmx,
  17. tmy == null ? Shader.TileMode.CLAMP : tmy));
  18. }
  19. state.mRebuildShader = false;
  20. }
  21. final int restoreAlpha;
  22. if (state.mBaseAlpha != 1.0f) {
  23. final Paint p = getPaint();
  24. restoreAlpha = p.getAlpha();
  25. p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
  26. } else {
  27. restoreAlpha = -1;
  28. }
  29. final boolean clearColorFilter;
  30. if (mTintFilter != null && paint.getColorFilter() == null) {
  31. paint.setColorFilter(mTintFilter);
  32. clearColorFilter = true;
  33. } else {
  34. clearColorFilter = false;
  35. }
  36. updateDstRectAndInsetsIfDirty();
  37. final Shader shader = paint.getShader();
  38. final boolean needMirroring = needMirroring();
  39. if (shader == null) {
  40. if (needMirroring) {
  41. canvas.save();
  42. // Mirror the bitmap
  43. canvas.translate(mDstRect.right - mDstRect.left, 0);
  44. canvas.scale(-1.0f, 1.0f);
  45. }
  46. canvas.drawBitmap(bitmap, null, mDstRect, paint);
  47. if (needMirroring) {
  48. canvas.restore();
  49. }
  50. } else {
  51. updateShaderMatrix(bitmap, paint, shader, needMirroring);
  52. canvas.drawRect(mDstRect, paint);
  53. }
  54. if (clearColorFilter) {
  55. paint.setColorFilter(null);
  56. }
  57. if (restoreAlpha >= 0) {
  58. paint.setAlpha(restoreAlpha);
  59. }
  60. }

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

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

  1. public void setImageBitmap(Bitmap bm) {
  2. // Hacky fix to force setImageDrawable to do a full setImageDrawable
  3. // instead of doing an object reference comparison
  4. mDrawable = null;
  5. if (mRecycleableBitmapDrawable == null) {
  6. mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
  7. } else {
  8. mRecycleableBitmapDrawable.setBitmap(bm);
  9. }
  10. setImageDrawable(mRecycleableBitmapDrawable);
  11. }
 
通过setImageBitmap 最终调用到 updateDrawable(Drawable d) 方法
  1. private void updateDrawable(Drawable d) {
  2. if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
  3. mRecycleableBitmapDrawable.setBitmap(null);
  4. }
  5. boolean sameDrawable = false;
  6. if (mDrawable != null) {
  7. sameDrawable = mDrawable == d;
  8. mDrawable.setCallback(null);
  9. unscheduleDrawable(mDrawable);
  10. if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
  11. mDrawable.setVisible(false, false);
  12. }
  13. }
  14. mDrawable = d;
  15. if (d != null) {
  16. d.setCallback(this);
  17. d.setLayoutDirection(getLayoutDirection());
  18. if (d.isStateful()) {
  19. d.setState(getDrawableState());
  20. }
  21. if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
  22. final boolean visible = sCompatDrawableVisibilityDispatch
  23. ? getVisibility() == VISIBLE
  24. : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
  25. d.setVisible(visible, true);
  26. }
  27. d.setLevel(mLevel);
  28. mDrawableWidth = d.getIntrinsicWidth();
  29. mDrawableHeight = d.getIntrinsicHeight();
  30. applyImageTint();
  31. applyColorMod();
  32. configureBounds();
  33. } else {
  34. mDrawableWidth = mDrawableHeight = -1;
  35. }
  36. }

 

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

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

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

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

先看下整体的效果图

效果1

先看下图形绘制:

  1. /**
  2. * 绘制一部分图形
  3. *
  4. * @param canvas
  5. */
  6. private void drawGraphics(Canvas canvas) {
  7. isFirst = false;
  8. Paint paint = new Paint();
  9. paint.setAntiAlias(true);//设置抗锯齿
  10. paint.setColor(Color.RED);//画笔颜色
  11. paint.setStyle(Paint.Style.STROKE);//空心画笔
  12. paint.setStrokeWidth(3); //设置笔刷的粗细
  13. canvas.drawCircle(40, 40, 30, paint);
  14. canvas.drawRect(10, 90, 70, 150, paint);
  15. canvas.drawRect(10, 170, 70, 200, paint);
  16. canvas.drawOval(new RectF(10, 220, 70, 250), paint);//椭圆
  17. /**
  18. * 使用path 画三角形
  19. */
  20. Path path = new Path();
  21. path.moveTo(10, 330);
  22. path.lineTo(40, 270);
  23. path.lineTo(70, 330);
  24. path.close();
  25. canvas.drawPath(path, paint);
  26. /**
  27. * 梯形
  28. */
  29. Path path1 = new Path();
  30. path1.moveTo(10, 410);
  31. path1.lineTo(25, 350);
  32. path1.lineTo(55, 350);
  33. path1.lineTo(70, 410);
  34. path1.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
  35. /*
  36. * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
  37. * 如果不设置close,图形不封闭。
  38. *
  39. * 当然,你也可以不设置close,再添加一条线,效果一样。
  40. */
  41. canvas.drawPath(path1, paint);
  42. /**
  43. * 第二列
  44. */
  45. paint.setColor(Color.BLUE);
  46. paint.setStyle(Paint.Style.FILL); //实心
  47. canvas.drawCircle(80 + 40, 40, 30, paint);
  48. canvas.drawRect(80 + 10, 90, 80 + 70, 150, paint);
  49. canvas.drawRect(80 + 10, 170, 80 + 70, 200, paint);
  50. canvas.drawOval(new RectF(80 + 10, 220, 80 + 70, 250), paint);//椭圆
  51. /**
  52. * 使用path 画三角形
  53. */
  54. Path path2 = new Path();
  55. path2.moveTo(80 + 10, 330);
  56. path2.lineTo(80 + 40, 270);
  57. path2.lineTo(80 + 70, 330);
  58. path2.close();
  59. canvas.drawPath(path2, paint);
  60. /**
  61. * 梯形
  62. */
  63. Path path3 = new Path();
  64. path3.moveTo(80 + 10, 410);
  65. path3.lineTo(80 + 25, 350);
  66. path3.lineTo(80 + 55, 350);
  67. path3.lineTo(80 + 70, 410);
  68. path3.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
  69. /*
  70. * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
  71. * 如果不设置close,图形不封闭。
  72. *
  73. * 当然,你也可以不设置close,再添加一条线,效果一样。
  74. */
  75. canvas.drawPath(path3, paint);
  76. /**
  77. * 第三列
  78. */
  79. /*
  80. * LinearGradient shader = new LinearGradient(0, 0, endX, endY, new
  81. * int[]{startColor, midleColor, endColor},new float[]{0 , 0.5f,
  82. * 1.0f}, TileMode.MIRROR);
  83. * 参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点
  84. * 其中参数new int[]{startColor, midleColor,endColor}是参与渐变效果的颜色集合,
  85. * 其中参数new float[]{0 , 0.5f, 1.0f}是定义每个颜色处于的渐变相对位置, 这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布
  86. */
  87. Shader mShader = new LinearGradient(0, 0, 100, 410, new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}, null, Shader.TileMode.REPEAT);
  88. // Shader.TileMode三种模式
  89. // REPEAT:沿着渐变方向循环重复
  90. // CLAMP:如果在预先定义的范围外画的话,就重复边界的颜色
  91. // MIRROR:与REPEAT一样都是循环重复,但这个会对称重复
  92. paint.setShader(mShader);
  93. canvas.drawCircle(160 + 40, 40, 30, paint);
  94. canvas.drawRect(160 + 10, 90, 160 + 70, 150, paint);
  95. canvas.drawRect(160 + 10, 170, 160 + 70, 200, paint);
  96. canvas.drawOval(new RectF(160 + 10, 220, 160 + 70, 250), paint);//椭圆
  97. /**
  98. * 使用path 画三角形
  99. */
  100. Path path4 = new Path();
  101. path4.moveTo(160 + 10, 330);
  102. path4.lineTo(160 + 40, 270);
  103. path4.lineTo(160 + 70, 330);
  104. path4.close();
  105. canvas.drawPath(path4, paint);
  106. /**
  107. * 梯形
  108. */
  109. Path path5 = new Path();
  110. path5.moveTo(160 + 10, 410);
  111. path5.lineTo(160 + 25, 350);
  112. path5.lineTo(160 + 55, 350);
  113. path5.lineTo(160 + 70, 410);
  114. path5.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
  115. /*
  116. * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
  117. * 如果不设置close,图形不封闭。
  118. *
  119. * 当然,你也可以不设置close,再添加一条线,效果一样。
  120. */
  121. canvas.drawPath(path5, paint);
  122. /**
  123. * 绘制文字
  124. * 第四列
  125. */
  126. paint.setTextSize(24);
  127. canvas.drawText("圆形", 240, 50, paint);
  128. canvas.drawText("正方形", 240, 120, paint);
  129. canvas.drawText("长方形", 240, 190, paint);
  130. canvas.drawText("椭圆形", 240, 250, paint);
  131. canvas.drawText("三角形", 240, 320, paint);
  132. canvas.drawText("梯形", 240, 390, paint);
  133. }

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

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

在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的中央,并且宽高撑满。

  1. Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
  2. float scoleW = width * 1.0f / bitmap.getWidth();
  3. float scoleH = height * 1.0f / bitmap.getHeight();
  4. // float scale = Math.max(scoleW, scoleH);
  5. matrix = new Matrix();
  6. /**
  7. * 先乘 平移
  8. */
  9. matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
  10. /**
  11. * 后乘 缩放
  12. */
  13. matrix.postScale(scoleW, scoleH, width / 2, height / 2);
  14. // matrix.setRotate(20);
  15. // matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
  16. // matrix.postRotate(-20);
  17. canvas.drawBitmap(bitmap, matrix, brushPaint);
  1. brushPaint = new Paint();
  2. brushPaint.setAntiAlias(true);
  3. brushPaint.setStyle(Paint.Style.STROKE);
  4. brushPaint.setStrokeCap(Paint.Cap.ROUND);
  5. brushPaint.setStrokeJoin(Paint.Join.ROUND);
  6. brushPaint.setColor(Color.RED);
  7. brushPaint.setStrokeWidth(4);

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

1.自定义按钮及事件

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

  1. /**
  2. * 画笔
  3. */
  4. Paint brushPaint;
  5. /**
  6. * 橡皮
  7. */
  8. Paint rubberPaint;
  9. /**
  10. * 画笔
  11. */
  12. brushPaint = new Paint();
  13. brushPaint.setAntiAlias(true);
  14. brushPaint.setStyle(Paint.Style.STROKE);
  15. brushPaint.setStrokeCap(Paint.Cap.ROUND);
  16. brushPaint.setStrokeJoin(Paint.Join.ROUND);
  17. brushPaint.setColor(Color.RED);
  18. brushPaint.setStrokeWidth(4);
  19. /**
  20. * 橡皮
  21. */
  22. rubberPaint = new Paint();
  23. //下面这句代码是橡皮擦设置的重点
  24. rubberPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
  25. rubberPaint.setStrokeWidth(20);
  26. rubberPaint.setAntiAlias(true);
  27. rubberPaint.setColor(Color.TRANSPARENT);
  28. rubberPaint.setStyle(Paint.Style.STROKE);
  29. rubberPaint.setStrokeJoin(Paint.Join.ROUND);
  30. rubberPaint.setAlpha(0);

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

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

  1. /**
  2. * 绘制带点击事件的按钮
  3. *
  4. * @param canvas
  5. */
  6. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
  7. private void drawButton(Canvas canvas) {
  8. targetRect = new RectF(width - 170, 10, width - 20, 100);
  9. Paint paint = new Paint();
  10. paint.setAntiAlias(true);//设置抗锯齿
  11. paint.setColor(Color.BLUE);//画笔颜色
  12. paint.setStyle(Paint.Style.FILL);//空心画笔
  13. paint.setStrokeWidth(3); //设置笔刷的粗细
  14. canvas.drawRoundRect(targetRect, 20, 20, paint);
  15. TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
  16. textPaint.setAntiAlias(true);//设置抗锯齿
  17. textPaint.setColor(Color.WHITE);//画笔颜色
  18. textPaint.setStyle(Paint.Style.FILL);
  19. textPaint.setStrokeWidth(2); //设置笔刷的粗细
  20. textPaint.setTextAlign(Paint.Align.CENTER);
  21. textPaint.setTextSize(28);
  22. Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
  23. float y = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
  24. canvas.drawText("橡皮", targetRect.centerX(), y, textPaint);
  25. targetRect1 = new RectF(targetRect.left, 110, targetRect.right, 200);
  26. canvas.drawRoundRect(targetRect1, 20, 20, paint);
  27. float yp = (targetRect1.bottom + targetRect1.top - fontMetrics.bottom - fontMetrics.top) / 2;
  28. canvas.drawText("画笔", targetRect.centerX(), yp, textPaint);
  29. }

通过

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

 

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

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

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

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

点击事件处理

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

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

2.绘制路径及橡皮

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

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

  1. public class MyDrawView extends View {
  2. boolean isFirst = true;
  3. /**
  4. * 画笔
  5. */
  6. Paint brushPaint;
  7. /**
  8. * 橡皮
  9. */
  10. Paint rubberPaint;
  11. Path path;
  12. private float downX, downY;
  13. int width, height;
  14. private RectF targetRect, targetRect1;
  15. private boolean isChangePaint = false;
  16. String paintType;
  17. Context context;
  18. Canvas mCanvas;
  19. Bitmap mBitmap;
  20. private float mX, mY;
  21. private static final float TOUCH_TOLERANCE = 4;
  22. private Matrix matrix;
  23. public enum PaintType {
  24. /**
  25. * 橡皮
  26. */
  27. RUBBER,
  28. /**
  29. * 画笔
  30. */
  31. BRUSH
  32. }
  33. public MyDrawView(Context context) {
  34. super(context);
  35. }
  36. private boolean pointInRect(float x, float y, RectF rectF) {
  37. if ((x > rectF.left && x < rectF.right) && (y > rectF.top && y < rectF.bottom)) {
  38. return true;
  39. }
  40. return false;
  41. }
  42. public MyDrawView(Context context, @Nullable AttributeSet attrs) {
  43. super(context, attrs);
  44. setFocusable(true);
  45. this.context = context;
  46. paintType = PaintType.BRUSH.name();
  47. width = getResources().getDisplayMetrics().widthPixels;
  48. height = getResources().getDisplayMetrics().heightPixels;
  49. initPaint();
  50. }
  51. private void initPaint() {
  52. path = new Path();
  53. /**
  54. * 画笔
  55. */
  56. brushPaint = new Paint();
  57. brushPaint.setAntiAlias(true);
  58. brushPaint.setStyle(Paint.Style.STROKE);
  59. brushPaint.setStrokeCap(Paint.Cap.ROUND);
  60. brushPaint.setStrokeJoin(Paint.Join.ROUND);
  61. brushPaint.setColor(Color.RED);
  62. brushPaint.setStrokeWidth(4);
  63. /**
  64. * 橡皮
  65. */
  66. rubberPaint = new Paint();
  67. //下面这句代码是橡皮擦设置的重点
  68. rubberPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
  69. rubberPaint.setStrokeWidth(20);
  70. rubberPaint.setAntiAlias(true);
  71. rubberPaint.setColor(Color.TRANSPARENT);
  72. rubberPaint.setStyle(Paint.Style.STROKE);
  73. rubberPaint.setStrokeJoin(Paint.Join.ROUND);
  74. rubberPaint.setAlpha(0);
  75. mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
  76. BitmapFactory.Options opts = new BitmapFactory.Options();
  77. opts.outHeight = height;
  78. opts.outWidth = width;
  79. mCanvas = new Canvas(mBitmap);
  80. }
  81. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
  82. protected void onDraw(Canvas canvas) {
  83. super.onDraw(canvas);
  84. drawGraphics(canvas);
  85. drawButton(canvas);
  86. if (mBitmap != null) {
  87. canvas.drawBitmap(mBitmap, 0, 0, brushPaint);
  88. }
  89. if (path != null) {
  90. if (paintType.equals(PaintType.BRUSH.name())) {
  91. mCanvas.drawPath(path, brushPaint);
  92. }
  93. if (paintType.equals(PaintType.RUBBER.name())) {
  94. mCanvas.drawPath(path, rubberPaint);
  95. }
  96. }
  97. }
  98. @Override
  99. public boolean onTouchEvent(MotionEvent event) {
  100. float x = event.getX();
  101. float y = event.getY();
  102. switch (event.getAction()) {
  103. case MotionEvent.ACTION_DOWN:
  104. downX = event.getX();
  105. downY = event.getY();
  106. touch_start(x, y);
  107. invalidate();
  108. break;
  109. case MotionEvent.ACTION_UP:
  110. buttonOnclick(event.getX(), event.getY());
  111. touch_up();
  112. invalidate();
  113. break;
  114. case MotionEvent.ACTION_MOVE:
  115. float moveX = event.getX();
  116. float moveY = event.getY();
  117. if (Math.abs(moveX - downX) > 10f || Math.abs(moveY - downY) > 10f) {
  118. // path.lineTo(moveX, moveY);
  119. isChangePaint = true;
  120. } else {
  121. isChangePaint = false;
  122. }
  123. touch_move(x, y);
  124. invalidate();
  125. break;
  126. }
  127. return true;
  128. }
  129. private void touch_start(float x, float y) {
  130. path.reset();
  131. path.moveTo(x, y);
  132. mX = x;
  133. mY = y;
  134. }
  135. private void touch_move(float x, float y) {
  136. float dx = Math.abs(x - mX);
  137. float dy = Math.abs(y - mY);
  138. if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
  139. path.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2);
  140. mX = x;
  141. mY = y;
  142. }
  143. }
  144. private void touch_up() {
  145. path.lineTo(mX, mY);
  146. }
  147. private void buttonOnclick(float x, float y) {
  148. if (!isChangePaint) {
  149. return;
  150. }
  151. if (pointInRect(x, y, targetRect1)) {
  152. paintType = PaintType.BRUSH.name();
  153. Toast.makeText(context, "画笔", Toast.LENGTH_SHORT).show();
  154. } else if (pointInRect(x, y, targetRect)) {
  155. paintType = PaintType.RUBBER.name();
  156. Toast.makeText(context, "橡皮", Toast.LENGTH_SHORT).show();
  157. }
  158. }
  159. /**
  160. * 绘制一部分图形
  161. *
  162. * @param canvas
  163. */
  164. private void drawGraphics(Canvas canvas) {
  165. isFirst = false;
  166. Paint paint = new Paint();
  167. Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
  168. float scoleW = width * 1.0f / bitmap.getWidth();
  169. float scoleH = height * 1.0f / bitmap.getHeight();
  170. // float scale = Math.max(scoleW, scoleH);
  171. matrix = new Matrix();
  172. /**
  173. * 先乘 平移
  174. */
  175. matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
  176. /**
  177. * 后乘 缩放
  178. */
  179. matrix.postScale(scoleW, scoleH, width / 2, height / 2);
  180. // matrix.setRotate(20);
  181. // matrix.preTranslate(width / 2 - bitmap.getWidth() / 2, height / 2 - bitmap.getHeight() / 2);
  182. // matrix.postRotate(-20);
  183. canvas.drawBitmap(bitmap, matrix, brushPaint);
  184. paint.setAntiAlias(true);//设置抗锯齿
  185. paint.setColor(Color.RED);//画笔颜色
  186. paint.setStyle(Paint.Style.STROKE);//空心画笔
  187. paint.setStrokeWidth(3); //设置笔刷的粗细
  188. canvas.drawCircle(40, 40, 30, paint);
  189. canvas.drawRect(10, 90, 70, 150, paint);
  190. canvas.drawRect(10, 170, 70, 200, paint);
  191. canvas.drawOval(new RectF(10, 220, 70, 250), paint);//椭圆
  192. /**
  193. * 使用path 画三角形
  194. */
  195. Path path = new Path();
  196. path.moveTo(10, 330);
  197. path.lineTo(40, 270);
  198. path.lineTo(70, 330);
  199. path.close();
  200. canvas.drawPath(path, paint);
  201. /**
  202. * 梯形
  203. */
  204. Path path1 = new Path();
  205. path1.moveTo(10, 410);
  206. path1.lineTo(25, 350);
  207. path1.lineTo(55, 350);
  208. path1.lineTo(70, 410);
  209. path1.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
  210. /*
  211. * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
  212. * 如果不设置close,图形不封闭。
  213. *
  214. * 当然,你也可以不设置close,再添加一条线,效果一样。
  215. */
  216. canvas.drawPath(path1, paint);
  217. /**
  218. * 第二列
  219. */
  220. paint.setColor(Color.BLUE);
  221. paint.setStyle(Paint.Style.FILL); //实心
  222. canvas.drawCircle(80 + 40, 40, 30, paint);
  223. canvas.drawRect(80 + 10, 90, 80 + 70, 150, paint);
  224. canvas.drawRect(80 + 10, 170, 80 + 70, 200, paint);
  225. canvas.drawOval(new RectF(80 + 10, 220, 80 + 70, 250), paint);//椭圆
  226. /**
  227. * 使用path 画三角形
  228. */
  229. Path path2 = new Path();
  230. path2.moveTo(80 + 10, 330);
  231. path2.lineTo(80 + 40, 270);
  232. path2.lineTo(80 + 70, 330);
  233. path2.close();
  234. canvas.drawPath(path2, paint);
  235. /**
  236. * 梯形
  237. */
  238. Path path3 = new Path();
  239. path3.moveTo(80 + 10, 410);
  240. path3.lineTo(80 + 25, 350);
  241. path3.lineTo(80 + 55, 350);
  242. path3.lineTo(80 + 70, 410);
  243. path3.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
  244. /*
  245. * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
  246. * 如果不设置close,图形不封闭。
  247. *
  248. * 当然,你也可以不设置close,再添加一条线,效果一样。
  249. */
  250. canvas.drawPath(path3, paint);
  251. /**
  252. * 第三列
  253. */
  254. /*
  255. * LinearGradient shader = new LinearGradient(0, 0, endX, endY, new
  256. * int[]{startColor, midleColor, endColor},new float[]{0 , 0.5f,
  257. * 1.0f}, TileMode.MIRROR);
  258. * 参数一为渐变起初点坐标x位置,参数二为y轴位置,参数三和四分辨对应渐变终点
  259. * 其中参数new int[]{startColor, midleColor,endColor}是参与渐变效果的颜色集合,
  260. * 其中参数new float[]{0 , 0.5f, 1.0f}是定义每个颜色处于的渐变相对位置, 这个参数可以为null,如果为null表示所有的颜色按顺序均匀的分布
  261. */
  262. Shader mShader = new LinearGradient(0, 0, 100, 410, new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW}, null, Shader.TileMode.REPEAT);
  263. // Shader.TileMode三种模式
  264. // REPEAT:沿着渐变方向循环重复
  265. // CLAMP:如果在预先定义的范围外画的话,就重复边界的颜色
  266. // MIRROR:与REPEAT一样都是循环重复,但这个会对称重复
  267. paint.setShader(mShader);
  268. canvas.drawCircle(160 + 40, 40, 30, paint);
  269. canvas.drawRect(160 + 10, 90, 160 + 70, 150, paint);
  270. canvas.drawRect(160 + 10, 170, 160 + 70, 200, paint);
  271. canvas.drawOval(new RectF(160 + 10, 220, 160 + 70, 250), paint);//椭圆
  272. /**
  273. * 使用path 画三角形
  274. */
  275. Path path4 = new Path();
  276. path4.moveTo(160 + 10, 330);
  277. path4.lineTo(160 + 40, 270);
  278. path4.lineTo(160 + 70, 330);
  279. path4.close();
  280. canvas.drawPath(path4, paint);
  281. /**
  282. * 梯形
  283. */
  284. Path path5 = new Path();
  285. path5.moveTo(160 + 10, 410);
  286. path5.lineTo(160 + 25, 350);
  287. path5.lineTo(160 + 55, 350);
  288. path5.lineTo(160 + 70, 410);
  289. path5.close();//把开始的点和最后的点连接在一起,构成一个封闭图形
  290. /*
  291. * 最重要的就是movtTo和close,如果是Style.FILL的话,不设置close,也没有区别,可是如果是STROKE模式,
  292. * 如果不设置close,图形不封闭。
  293. *
  294. * 当然,你也可以不设置close,再添加一条线,效果一样。
  295. */
  296. canvas.drawPath(path5, paint);
  297. /**
  298. * 绘制文字
  299. * 第四列
  300. */
  301. paint.setTextSize(24);
  302. canvas.drawText("圆形", 240, 50, paint);
  303. canvas.drawText("正方形", 240, 120, paint);
  304. canvas.drawText("长方形", 240, 190, paint);
  305. canvas.drawText("椭圆形", 240, 250, paint);
  306. canvas.drawText("三角形", 240, 320, paint);
  307. canvas.drawText("梯形", 240, 390, paint);
  308. }
  309. /**
  310. * 绘制带点击事件的按钮
  311. *
  312. * @param canvas
  313. */
  314. @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
  315. private void drawButton(Canvas canvas) {
  316. targetRect = new RectF(width - 170, 10, width - 20, 100);
  317. Paint paint = new Paint();
  318. paint.setAntiAlias(true);//设置抗锯齿
  319. paint.setColor(Color.BLUE);//画笔颜色
  320. paint.setStyle(Paint.Style.FILL);//空心画笔
  321. paint.setStrokeWidth(3); //设置笔刷的粗细
  322. canvas.drawRoundRect(targetRect, 10, 20, paint);
  323. TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
  324. textPaint.setAntiAlias(true);//设置抗锯齿
  325. textPaint.setColor(Color.WHITE);//画笔颜色
  326. textPaint.setStyle(Paint.Style.FILL);
  327. textPaint.setStrokeWidth(2); //设置笔刷的粗细
  328. textPaint.setTextAlign(Paint.Align.CENTER);
  329. textPaint.setTextSize(28);
  330. Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
  331. float y = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
  332. canvas.drawText("橡皮", targetRect.centerX(), y, textPaint);
  333. targetRect1 = new RectF(targetRect.left, 110, targetRect.right, 200);
  334. canvas.drawRoundRect(targetRect1, 20, 20, paint);
  335. float yp = (targetRect1.bottom + targetRect1.top - fontMetrics.bottom - fontMetrics.top) / 2;
  336. canvas.drawText("画笔", targetRect.centerX(), yp, textPaint);
  337. }
  338. }

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

参考:

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

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

 

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号