Region.Op - canvas.clipxxx(xxx, Region.Op.XXX)

注: **canvas.clipxxx()** 对于圆角部分锯齿有些明显, Android9.0之后仅支持 **Region.Op.DIFFERENCE/Region.Op.INTERSECT**
Region.Op 主要用于 canvas 的裁剪, 与其类似的还有 Path.Op 作用于 Path 上, 具体效果请参考下文的 Path.Op

  1. @Override
  2. protected void onDraw(Canvas canvas) {
  3. super.onDraw(canvas);
  4. canvas.save();
  5. canvas.clipPath(mBgPath);
  6. // canvas.clipPath(mBgPath, Region.Op.DIFFERENCE)
  7. canvas.drawPath(mBgPath, mPaintBg);
  8. canvas.drawRect(mRectF, mPaint);
  9. canvas.restore();
  10. }

PorterDuff.Mode - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.XXX)

注: PorterDuff 拥有抗锯齿属性
SRC: 设置了 xfermode 的所有图形(目标图形, 设置顺序会有)
DST: 没有设置 xfermode 的所有图形
PorterDuff.Mode 的合成模式主要依靠透明重叠, 如果非重叠部分不透明, 非重叠部分也有可能会显示出来, 结果可能会跟预想的不同, 解决方法就是在非重叠部分添加透明层(注意: 透明层的添加顺序, 透明层是否设置 Xfermode)image.png

  1. // PorterDuff 示例 View
  2. class PorterDuffView constructor(context: Context, attrs: AttributeSet? = null) : View(context, attrs) {
  3. private var type = 0
  4. private val xfermode: Xfermode by lazy {
  5. val mode = when (type) {
  6. 0 -> {
  7. PorterDuff.Mode.CLEAR
  8. }
  9. 1 -> {
  10. PorterDuff.Mode.SRC
  11. }
  12. 2 -> {
  13. PorterDuff.Mode.DST
  14. }
  15. 3 -> {
  16. PorterDuff.Mode.SRC_OVER
  17. }
  18. 4 -> {
  19. PorterDuff.Mode.DST_OVER
  20. }
  21. 5 -> {
  22. PorterDuff.Mode.SRC_IN
  23. }
  24. 6 -> {
  25. PorterDuff.Mode.DST_IN
  26. }
  27. 7 -> {
  28. PorterDuff.Mode.SRC_OUT
  29. }
  30. 8 -> {
  31. PorterDuff.Mode.DST_OUT
  32. }
  33. 9 -> {
  34. PorterDuff.Mode.SRC_ATOP
  35. }
  36. 10 -> {
  37. PorterDuff.Mode.DST_ATOP
  38. }
  39. 11 -> {
  40. PorterDuff.Mode.XOR
  41. }
  42. 16 -> {
  43. PorterDuff.Mode.DARKEN
  44. }
  45. 17 -> {
  46. PorterDuff.Mode.LIGHTEN
  47. }
  48. 13 -> {
  49. PorterDuff.Mode.MULTIPLY
  50. }
  51. 14 -> {
  52. PorterDuff.Mode.SCREEN
  53. }
  54. 12 -> {
  55. PorterDuff.Mode.ADD
  56. }
  57. else -> {
  58. PorterDuff.Mode.OVERLAY
  59. }
  60. }
  61. PorterDuffXfermode(mode)
  62. }
  63. private val srcColor = Color.parseColor("#FF2C98F0")
  64. private val dstColor = Color.parseColor("#FFff2565")
  65. private val transparentColor = 0
  66. private val area = dp2px(100f)
  67. private val offset = dp2px(10f)
  68. private val radius = dp2px(30f)
  69. private val size = dp2px(50f)
  70. private val paint = Paint().apply {
  71. isAntiAlias = true
  72. style = Paint.Style.FILL_AND_STROKE
  73. strokeWidth = 1f
  74. }
  75. private val path = Path()
  76. private val rectF = RectF(offset, area - offset - size, offset + size, area - offset)
  77. private val rectFBg = RectF(0f, 0f, area, area)
  78. init {
  79. attrs?.let {
  80. val types = context.obtainStyledAttributes(it, R.styleable.PorterDuffView)
  81. type = types.getInt(R.styleable.PorterDuffView_porter_type, type)
  82. types.recycle()
  83. }
  84. }
  85. override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
  86. setMeasuredDimension(area.toInt(), area.toInt())
  87. }
  88. override fun onDraw(canvas: Canvas) {
  89. val count = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG)
  90. path.reset()
  91. path.addCircle(area - offset - radius, radius + offset, radius, Path.Direction.CCW)
  92. paint.color = dstColor
  93. canvas.drawPath(path, paint)
  94. path.reset()
  95. path.addRect(rectF, Path.Direction.CCW)
  96. paint.color = srcColor
  97. paint.xfermode = xfermode
  98. canvas.drawPath(path, paint)
  99. paint.xfermode = null
  100. path.reset()
  101. path.addRect(rectFBg, Path.Direction.CCW)
  102. path.addRect(rectF, Path.Direction.CW)
  103. paint.color = transparentColor
  104. paint.xfermode = xfermode
  105. canvas.drawPath(path, paint)
  106. paint.xfermode = null
  107. canvas.restoreToCount(count)
  108. }
  109. private fun dp2px(dpValue: Float): Float {
  110. val scale = Resources.getSystem().displayMetrics.density
  111. return dpValue * scale + 0.5f
  112. }
  113. }
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="PorterDuffView">
  4. <attr name="porter_type" format="enum" >
  5. <enum name="clear" value="0" />
  6. <enum name="src" value="1" />
  7. <enum name="dst" value="2" />
  8. <enum name="src_over" value="3" />
  9. <enum name="dst_over" value="4" />
  10. <enum name="src_in" value="5" />
  11. <enum name="dst_in" value="6" />
  12. <enum name="src_out" value="7" />
  13. <enum name="dst_out" value="8" />
  14. <enum name="src_atop" value="9" />
  15. <enum name="dst_atop" value="10" />
  16. <enum name="xor" value="11" />
  17. <enum name="darken" value="16" />
  18. <enum name="lighten" value="17" />
  19. <enum name="multiply" value="13" />
  20. <enum name="screen" value="14" />
  21. <enum name="add" value="12" />
  22. <enum name="overlay" value="15" />
  23. </attr>
  24. </declare-styleable>
  25. </resources>
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:background="@color/white"
  7. android:orientation="vertical">
  8. <com.ypw.code.view.FlowLayout
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content">
  11. <com.ypw.code.view.PorterDuffView
  12. android:layout_width="wrap_content"
  13. android:layout_height="wrap_content"
  14. android:layout_margin="@dimen/sw_2"
  15. android:background="#f0eeeeee"
  16. app:porter_type="clear" />
  17. <com.ypw.code.view.PorterDuffView
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"
  20. android:layout_margin="@dimen/sw_2"
  21. android:background="#f0eeeeee"
  22. app:porter_type="src" />
  23. <com.ypw.code.view.PorterDuffView
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:layout_margin="@dimen/sw_2"
  27. android:background="#f0eeeeee"
  28. app:porter_type="dst" />
  29. <com.ypw.code.view.PorterDuffView
  30. android:layout_width="wrap_content"
  31. android:layout_height="wrap_content"
  32. android:layout_margin="@dimen/sw_2"
  33. android:background="#f0eeeeee"
  34. app:porter_type="src_over" />
  35. <com.ypw.code.view.PorterDuffView
  36. android:layout_width="wrap_content"
  37. android:layout_height="wrap_content"
  38. android:layout_margin="@dimen/sw_2"
  39. android:background="#f0eeeeee"
  40. app:porter_type="dst_over" />
  41. <com.ypw.code.view.PorterDuffView
  42. android:layout_width="wrap_content"
  43. android:layout_height="wrap_content"
  44. android:layout_margin="@dimen/sw_2"
  45. android:background="#f0eeeeee"
  46. app:porter_type="src_in" />
  47. <com.ypw.code.view.PorterDuffView
  48. android:layout_width="wrap_content"
  49. android:layout_height="wrap_content"
  50. android:layout_margin="@dimen/sw_2"
  51. android:background="#f0eeeeee"
  52. app:porter_type="dst_in" />
  53. <com.ypw.code.view.PorterDuffView
  54. android:layout_width="wrap_content"
  55. android:layout_height="wrap_content"
  56. android:layout_margin="@dimen/sw_2"
  57. android:background="#f0eeeeee"
  58. app:porter_type="src_out" />
  59. <com.ypw.code.view.PorterDuffView
  60. android:layout_width="wrap_content"
  61. android:layout_height="wrap_content"
  62. android:layout_margin="@dimen/sw_2"
  63. android:background="#f0eeeeee"
  64. app:porter_type="dst_out" />
  65. <com.ypw.code.view.PorterDuffView
  66. android:layout_width="wrap_content"
  67. android:layout_height="wrap_content"
  68. android:layout_margin="@dimen/sw_2"
  69. android:background="#f0eeeeee"
  70. app:porter_type="src_atop" />
  71. <com.ypw.code.view.PorterDuffView
  72. android:layout_width="wrap_content"
  73. android:layout_height="wrap_content"
  74. android:layout_margin="@dimen/sw_2"
  75. android:background="#f0eeeeee"
  76. app:porter_type="dst_atop" />
  77. <com.ypw.code.view.PorterDuffView
  78. android:layout_width="wrap_content"
  79. android:layout_height="wrap_content"
  80. android:layout_margin="@dimen/sw_2"
  81. android:background="#f0eeeeee"
  82. app:porter_type="xor" />
  83. <com.ypw.code.view.PorterDuffView
  84. android:layout_width="wrap_content"
  85. android:layout_height="wrap_content"
  86. android:layout_margin="@dimen/sw_2"
  87. android:background="#f0eeeeee"
  88. app:porter_type="darken" />
  89. <com.ypw.code.view.PorterDuffView
  90. android:layout_width="wrap_content"
  91. android:layout_height="wrap_content"
  92. android:layout_margin="@dimen/sw_2"
  93. android:background="#f0eeeeee"
  94. app:porter_type="lighten" />
  95. <com.ypw.code.view.PorterDuffView
  96. android:layout_width="wrap_content"
  97. android:layout_height="wrap_content"
  98. android:layout_margin="@dimen/sw_2"
  99. android:background="#f0eeeeee"
  100. app:porter_type="multiply" />
  101. <com.ypw.code.view.PorterDuffView
  102. android:layout_width="wrap_content"
  103. android:layout_height="wrap_content"
  104. android:layout_margin="@dimen/sw_2"
  105. android:background="#f0eeeeee"
  106. app:porter_type="screen" />
  107. <com.ypw.code.view.PorterDuffView
  108. android:layout_width="wrap_content"
  109. android:layout_height="wrap_content"
  110. android:layout_margin="@dimen/sw_2"
  111. android:background="#f0eeeeee"
  112. app:porter_type="overlay" />
  113. <com.ypw.code.view.PorterDuffView
  114. android:layout_width="wrap_content"
  115. android:layout_height="wrap_content"
  116. android:layout_margin="@dimen/sw_2"
  117. android:background="#f0eeeeee"
  118. app:porter_type="add" />
  119. </com.ypw.code.view.FlowLayout>
  120. </androidx.core.widget.NestedScrollView>

Path.fillType + Path.Direction.CW/CCW

path.fillType 路径的填充方式:

  • WINDING: 非零环绕, 保留两种图形的全部区域
  • INVERSEWINDING: 反非零环绕, **保留非两种图形的区域**_
  • EVENODD: 奇偶, **去除第二种图形和第一种图形的重叠部分**_
  • INVERSEEVEN_ODD: 反奇偶, **填充非图形区域并去除两种图形的非重叠部分_**

path.addxxx(xxx, Path.Direction.CW/CCW) 路径的绘制方向

  • Path.Direction.CW: 顺时针方向
  • Path.Direction.CCW: 逆时针方向

path.fillType 可单独使用, 也可以和 path.addxxx(xxx, Path.Direction.CW/CCW) 一起使用

第一种情况: 两种图形里有一个带圆角, 且是同向绘制

image.png

第二种情况: 两种图形里有一个带圆角, 且是异向绘制

image.png

第三种情况: 两种图形里不包含圆角, 且是同向绘制

image.png

第四种情况: 两种图形里不包含圆角, 且是异向绘制

image.png

  1. // 正方形和圆角正方形
  2. override fun onDraw(canvas: Canvas) {
  3. val size = min(width, height) / 4f
  4. val path = Path()
  5. val rectF = RectF(size / 2, size / 2, width - size / 2, height - size / 2)
  6. path.fillType = dir
  7. path.addRect(rectF, Path.Direction.CW)
  8. path.addRoundRect(rectF, size / 2, size / 2, Path.Direction.CW)
  9. canvas.drawPath(path, paint)
  10. }
  11. // 两个圆形
  12. override fun onDraw(canvas: Canvas) {
  13. val centerX = width / 2f
  14. val centerY = height / 2f
  15. val size = min(centerX, centerY) / 2f
  16. val path = Path()
  17. path.fillType = dir
  18. path.addCircle(centerX - size / 2, centerY, size, Path.Direction.CW)
  19. path.addCircle(centerX + size / 2, centerY, size, Path.Direction.CCW)
  20. canvas.drawPath(path, paint)
  21. }
  22. // 正方形和五角星
  23. override fun onDraw(canvas: Canvas) {
  24. val pathData = context.getString(R.string.path_star)
  25. // 根据 矢量图形里的 PathData 创建 Path
  26. val pathStart = PathUtils.createPathFromPathData(pathData)
  27. // 应用 Matrix
  28. if (pathStart != null) {
  29. // 计算缩放比例
  30. val scaleWidth: Float = width / 1024.0f
  31. val scaleHeight: Float = height / 1024.0f
  32. // 创建 Matrix 处理 path 缩放
  33. val matrix = Matrix()
  34. matrix.postScale(scaleWidth, scaleHeight)
  35. pathStart.transform(matrix)
  36. }
  37. val size = min(width, height) / 4f
  38. val path = Path()
  39. val rectF = RectF(size / 2, size / 2, width - size / 2, height - size / 2)
  40. val rectF1 = RectF(size, size, width - size, height - size)
  41. path.fillType = dir
  42. path.addRect(rectF, Path.Direction.CCW)
  43. path.addPath(pathStart)
  44. canvas.drawPath(path, paint)
  45. }
  46. // 大小正方形
  47. override fun onDraw(canvas: Canvas) {
  48. val size = min(width, height) / 4f
  49. val path = Path()
  50. val rectF = RectF(size / 2, size / 2, width - size / 2, height - size / 2)
  51. val rectF1 = RectF(size, size, width - size, height - size)
  52. path.fillType = dir
  53. path.addRect(rectF1, Path.Direction.CCW)
  54. path.addRect(rectF, Path.Direction.CW)
  55. canvas.drawPath(path, paint)
  56. }

Path.Op - path.op(xxx, Path.Op.XXX)

注: Path.Op 添加于 Android4.4 - API 19
与 Region.Op 类似, 只是少了一个 REPLACE, Region.Op
image.png

  1. class TempTextView constructor(context: Context, attrs: AttributeSet? = null) : TextView(context, attrs) {
  2. val paint = Paint().apply {
  3. isAntiAlias = true
  4. color = Color.parseColor("#FF2C98F0")
  5. style = Paint.Style.FILL
  6. }
  7. val pathRect1: Path by lazy {
  8. Path().apply {
  9. addRect(0f, 0f, width * 0.6f, height * 0.6f, Path.Direction.CW)
  10. }
  11. }
  12. val pathRect2: Path by lazy {
  13. Path().apply {
  14. addRect(width * 0.4f, height * 0.4f, width.toFloat(), height.toFloat(), Path.Direction.CW)
  15. }
  16. }
  17. var type = 0
  18. private val op: Path.Op by lazy {
  19. val p = when (type) {
  20. 0 -> {
  21. Path.Op.DIFFERENCE
  22. }
  23. 1 -> {
  24. Path.Op.INTERSECT
  25. }
  26. 2 -> {
  27. Path.Op.UNION
  28. }
  29. 3 -> {
  30. Path.Op.XOR
  31. }
  32. else -> {
  33. Path.Op.REVERSE_DIFFERENCE
  34. }
  35. }
  36. text = p.name
  37. p
  38. }
  39. init {
  40. attrs?.let {
  41. val types = context.obtainStyledAttributes(it, R.styleable.TempTextView)
  42. type = types.getInt(R.styleable.TempTextView_op_type, type)
  43. types.recycle()
  44. }
  45. setLayerType(LAYER_TYPE_SOFTWARE, null)
  46. }
  47. override fun onDraw(canvas: Canvas) {
  48. val path = Path()
  49. path.op(pathRect1, pathRect2, op)
  50. canvas.drawPath(path, paint)
  51. super.onDraw(canvas)
  52. }
  53. }
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="TempTextView">
  4. <attr name="op_type" format="enum">
  5. <enum name="DIFFERENCE" value="0"/>
  6. <enum name="INTERSECT" value="1"/>
  7. <enum name="UNION" value="2"/>
  8. <enum name="XOR" value="3"/>
  9. <enum name="REVERSE_DIFFERENCE" value="4"/>
  10. <enum name="REPLACE" value="5"/>
  11. </attr>
  12. </declare-styleable>
  13. </resources>
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:background="@color/white"
  7. android:orientation="vertical">
  8. <com.ypw.code.view.FlowLayout
  9. android:layout_width="match_parent"
  10. android:layout_height="wrap_content">
  11. <com.ypw.code.view.TempTextView
  12. android:layout_width="@dimen/sw_160"
  13. android:layout_height="@dimen/sw_160"
  14. android:textSize="@dimen/sw_16"
  15. android:gravity="center"
  16. android:layout_margin="@dimen/sw_5"
  17. android:background="#80eeeeee"
  18. app:op_type="DIFFERENCE"/>
  19. <com.ypw.code.view.TempTextView
  20. android:layout_width="@dimen/sw_160"
  21. android:layout_height="@dimen/sw_160"
  22. android:textSize="@dimen/sw_16"
  23. android:gravity="center"
  24. android:layout_margin="@dimen/sw_5"
  25. android:background="#80eeeeee"
  26. app:op_type="INTERSECT"/>
  27. <com.ypw.code.view.TempTextView
  28. android:layout_width="@dimen/sw_160"
  29. android:layout_height="@dimen/sw_160"
  30. android:textSize="@dimen/sw_16"
  31. android:gravity="center"
  32. android:layout_margin="@dimen/sw_5"
  33. android:background="#80eeeeee"
  34. app:op_type="UNION"/>
  35. <com.ypw.code.view.TempTextView
  36. android:layout_width="@dimen/sw_160"
  37. android:layout_height="@dimen/sw_160"
  38. android:textSize="@dimen/sw_16"
  39. android:layout_margin="@dimen/sw_5"
  40. android:background="#80eeeeee"
  41. android:gravity="center"
  42. app:op_type="XOR"/>
  43. <com.ypw.code.view.TempTextView
  44. android:layout_width="@dimen/sw_160"
  45. android:layout_height="@dimen/sw_160"
  46. android:textSize="@dimen/sw_16"
  47. android:layout_margin="@dimen/sw_5"
  48. android:background="#80eeeeee"
  49. android:gravity="center"
  50. app:op_type="REVERSE_DIFFERENCE"/>
  51. </com.ypw.code.view.FlowLayout>
  52. </androidx.core.widget.NestedScrollView>