上一篇 Android 自定义View 实例_ 画图 参考: https://blog.csdn.net/whjk20/article/details/115639448
这里是Canvas 的裁剪 (同时可以控制可见性, 主要是考虑到绘制的性能)
目录
(3)overdraw 过度绘制 (减少过度绘制,提升性能)
1. 相关概念
(1)Render 渲染
https://developer.android.com/topic/performance/rendering
(2)Clipping 裁剪 (只绘制可见部分,提升性能)
https://en.wikipedia.org/wiki/Clipping_%28computer_graphics%29
(3)overdraw 过度绘制 (减少过度绘制,提升性能)
https://developer.android.com/topic/performance/rendering/overdraw.html
过度绘制是指系统在渲染单个帧的过程中多次在屏幕上绘制某一个像素。
例如,如果我们有若干界面卡片堆叠在一起,每张卡片都会遮盖其下面一张卡片的部分内容。
您可以采取以下几种策略来减少甚至消除过度绘制:
- (1)移除布局中不需要的背景。
- (2)使视图层次结构扁平化。
- (3)降低透明度:
例如,如需获得灰色文本,您可以在 TextView 中绘制黑色文本,再为其设置半透明的透明度值。但是,您可以简单地通过用灰色绘制文本来获得同样的效果,而且能够大幅提升性能。
2. 屏幕分辩率
Smallest Screen Width -> 480 -> sw-480
Screen Width -> 480 ->w-480
3. The Drawing Algorithm
- canvas.save() 保存当前状态
- canvas.translate() 移动到特定的坐标
- canvas.clipRect()/clipOutRect 相对当前画布,不进行绘制的部分(裁剪部分)
- draw
- canvas.restore
What are advantages of moving the canvas instead of moving the objects?
Simplicity ,safty, less error prone
Easier to unwind transformations
4. Clipping methods
1. save current state: canvas.save()
2. Translate origin to row/column coords: canvas.translate()
3. Apply transformations to path
4. Apply clipping: canvas.clipPath(path) 重叠的部分只画path
5. Draw shape: drawClippedRectangle or drawText()
6. Restore previous canvas state: canvas.restore()
5. 实例
5.1 效果图:
5.2 代码
主要是自定义View, 主要是onDraw() 里面的10个方法, 对应上面效果图
package com.example.clippingexample
import android.content.Context
import android.graphics.*
import android.os.Build
import android.util.AttributeSet
import android.view.View
class ClippedView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint().apply {
// Smooth out edges of what is drawn without affecting shape.
isAntiAlias = true
strokeWidth = resources.getDimension(R.dimen.strokeWidth)
textSize = resources.getDimension(R.dimen.textSize)
}
private val path = Path() //把已经绘制的轨迹保存到本地
//裁剪矩形
private val clipRectRight = resources.getDimension(R.dimen.clipRectRight)
private val clipRectBottom = resources.getDimension(R.dimen.clipRectBottom)
private val clipRectTop = resources.getDimension(R.dimen.clipRectTop)
private val clipRectLeft = resources.getDimension(R.dimen.clipRectLeft)
//矩形内部
private val rectInset = resources.getDimension(R.dimen.rectInset)
private val smallRectOffset = resources.getDimension(R.dimen.smallRectOffset)
//在矩形的圆
private val circleRadius = resources.getDimension(R.dimen.circleRadius)
private val textOffset = resources.getDimension(R.dimen.textOffset)
private val textSize = resources.getDimension(R.dimen.textSize)
//行列位置
private val columnOne = rectInset
private val columnTwo = columnOne + rectInset + clipRectRight
private val rowOne = rectInset
private val rowTwo = rowOne + rectInset + clipRectBottom
private val rowThree = rowTwo + rectInset + clipRectBottom
private val rowFour = rowThree + rectInset + clipRectBottom
private val textRow = rowFour + (1.5f * clipRectBottom)
private val rejectRow = rowFour + rectInset + 2*clipRectBottom
private var rectF = RectF(
rectInset,
rectInset,
clipRectRight - rectInset,
clipRectBottom - rectInset
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawBackAndUnclippedRectangle(canvas)
drawDifferenceClippingExample(canvas)
drawCircularClippingExample(canvas)
drawIntersectionClippingExample(canvas)
drawCombinedClippingExample(canvas)
drawRoundedRectangleClippingExample(canvas)
drawOutsideClippingExample(canvas)
drawSkewedTextExample(canvas)
drawTranslatedTextExample(canvas)
drawQuickRejectExample(canvas)
}
private fun drawClippedRectangle(canvas: Canvas){
// 0, 0, 90/120, 90/120 裁剪一个矩形(正方形)
canvas.clipRect(clipRectLeft, clipRectTop, clipRectRight, clipRectBottom)
canvas.drawColor(Color.WHITE) //先绘制背景为白色
//绘制红线 , 开始点的x,y坐标, 终点的x,y坐标
paint.color=Color.RED
canvas.drawLine(clipRectLeft,clipRectTop,clipRectRight,clipRectBottom, paint)
//绘制一个圆, 颜色为绿色,圆心坐标及半径
paint.color=Color.GREEN
canvas.drawCircle(circleRadius,clipRectBottom-circleRadius,circleRadius,paint)
//绘制文本
paint.color=Color.BLUE
// Align the RIGHT side of the text with the origin. 参考对象为当前绘制区域, 右对齐,即文本靠齐右边框
paint.textSize=textSize
paint.textAlign=Paint.Align.RIGHT
canvas.drawText(context.getString(R.string.clipping), clipRectRight, textOffset, paint)
}
private fun drawBackAndUnclippedRectangle(canvas: Canvas) {
canvas.drawColor(Color.GRAY) //绘制背景为灰色
canvas.save() //保存canvas
canvas.translate(columnOne, rowOne) //移动canvas到下一个绘制点
drawClippedRectangle(canvas) //绘制
canvas.restore() //恢复canvas到原来的位置
}
private fun drawDifferenceClippingExample(canvas: Canvas) {
canvas.save()
// Move the origin to the right for the next rectangle.
canvas.translate(columnTwo,rowOne)
// Use the subtraction of two clipping rectangles to create a frame.
canvas.clipRect(
2 * rectInset,2 * rectInset,
clipRectRight - 2 * rectInset,
clipRectBottom - 2 * rectInset
)
// The method clipRect(float, float, float, float, Region.Op
// .DIFFERENCE) was deprecated in API level 26. The recommended
// alternative method is clipOutRect(float, float, float, float),
// which is currently available in API level 26 and higher.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
// 使得这部分保留灰色(即这部分不进行绘制?????, 还是把这层放在最上面绘制???)
canvas.clipRect(
4 * rectInset,4 * rectInset,
clipRectRight - 4 * rectInset,
clipRectBottom - 4 * rectInset,
Region.Op.DIFFERENCE //差异
)
else {
// 使得这部分保留灰色(即这部分不进行绘制?????)
canvas.clipOutRect(
4 * rectInset,4 * rectInset,
clipRectRight - 4 * rectInset,
clipRectBottom - 4 * rectInset
)
}
drawClippedRectangle(canvas)
canvas.restore()
}
private fun drawCircularClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnOne, rowTwo)
// Clears any lines and curves from the path but unlike reset(),
// keeps the internal data structure for faster reuse.
path.rewind()
path.addCircle( //重叠的部分,以这部分为准
circleRadius,clipRectBottom - circleRadius,
circleRadius,Path.Direction.CCW //逆时针
)
// The method clipPath(path, Region.Op.DIFFERENCE) was deprecated in
// API level 26. The recommended alternative method is
// clipOutPath(Path), which is currently available in
// API level 26 and higher.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
canvas.clipPath(path, Region.Op.DIFFERENCE)
} else {
canvas.clipOutPath(path)
}
drawClippedRectangle(canvas)
canvas.restore()
}
private fun drawIntersectionClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnTwo,rowTwo)
canvas.clipRect(
clipRectLeft,clipRectTop,
clipRectRight - smallRectOffset,
clipRectBottom - smallRectOffset
)
// The method clipRect(float, float, float, float, Region.Op
// .INTERSECT) was deprecated in API level 26. The recommended
// alternative method is clipRect(float, float, float, float), which
// is currently available in API level 26 and higher.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
canvas.clipRect(
clipRectLeft + smallRectOffset,
clipRectTop + smallRectOffset,
clipRectRight,clipRectBottom,
Region.Op.INTERSECT //交集- 相交刚好是绿色圆的部分, 其它的均为灰色,即不绘制???
)
} else {
canvas.clipRect(
clipRectLeft + smallRectOffset,
clipRectTop + smallRectOffset,
clipRectRight,clipRectBottom
)
}
drawClippedRectangle(canvas)
canvas.restore()
}
private fun drawCombinedClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnOne, rowThree)
path.rewind()
path.addCircle(
clipRectLeft + rectInset + circleRadius,
clipRectTop + circleRadius + rectInset,
circleRadius,Path.Direction.CCW
)
path.addRect(
clipRectRight / 2 - circleRadius,
clipRectTop + circleRadius + rectInset,
clipRectRight / 2 + circleRadius,
clipRectBottom - rectInset,Path.Direction.CCW
)
canvas.clipPath(path) // 新的圆、正方形覆盖原来的,这两个形状以外的部分为灰色
drawClippedRectangle(canvas)
canvas.restore()
}
private fun drawRoundedRectangleClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnTwo,rowThree)
path.rewind()
// rectF 区域, rx 半径, ry 半径, 方向
path.addRoundRect(
rectF,clipRectRight / 4,
clipRectRight / 4, Path.Direction.CCW
)
canvas.clipPath(path) // 圆角边框内的可见,其余的灰色
drawClippedRectangle(canvas)
canvas.restore()
}
//剪切可见的区域,其余为灰色
private fun drawOutsideClippingExample(canvas: Canvas) {
canvas.save()
canvas.translate(columnOne,rowFour)
canvas.clipRect(2 * rectInset,2 * rectInset,
clipRectRight - 2 * rectInset,
clipRectBottom - 2 * rectInset)
drawClippedRectangle(canvas)
canvas.restore()
}
// 文字
private fun drawTranslatedTextExample(canvas: Canvas) {
canvas.save()
paint.color = Color.GREEN
// Align the RIGHT side of the text with the origin.
paint.textAlign = Paint.Align.LEFT
// Apply transformation to canvas.
canvas.translate(columnTwo, textRow)
// Draw text.
canvas.drawText(
context.getString(R.string.translated),
clipRectLeft, clipRectTop, paint
)
canvas.restore()
}
//Skewed 斜体
private fun drawSkewedTextExample(canvas: Canvas) {
canvas.save()
paint.color = Color.YELLOW
paint.textAlign = Paint.Align.RIGHT
// Position text.
canvas.translate(columnTwo, textRow)
// Apply skew transformation.
canvas.skew(0.2f, 0.3f) //斜率 ,x y
canvas.drawText(context.getString(R.string.skewed),
clipRectLeft, clipRectTop, paint)
canvas.restore()
}
private fun drawQuickRejectExample(canvas: Canvas) {
val inClipRectangle = RectF(clipRectRight / 2,
clipRectBottom / 2,
clipRectRight * 2,
clipRectBottom * 2)
val notInClipRectangle = RectF(RectF(clipRectRight+1,
clipRectBottom+1,
clipRectRight * 2,
clipRectBottom * 2))
canvas.save()
canvas.translate(columnOne, rejectRow)
canvas.clipRect(
clipRectLeft,clipRectTop,
clipRectRight,clipRectBottom
)
//如果完全不可见,则返回true, 只要有部分可见都返回false
if (canvas.quickReject(
inClipRectangle, Canvas.EdgeType.AA)) {
canvas.drawColor(Color.WHITE)
}
else {
canvas.drawColor(Color.BLACK)
canvas.drawRect(inClipRectangle, paint
)
}
canvas.restore()
}
}
其中,dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="clipRectRight">90dp</dimen>
<dimen name="clipRectBottom">90dp</dimen>
<dimen name="clipRectTop">0dp</dimen>
<dimen name="clipRectLeft">0dp</dimen>
<dimen name="rectInset">8dp</dimen>
<dimen name="smallRectOffset">40dp</dimen>
<dimen name="circleRadius">30dp</dimen>
<dimen name="textOffset">20dp</dimen>
<dimen name="strokeWidth">4dp</dimen>
<dimen name="textSize">18sp</dimen>
</resources>
strings.xml
<resources>
<string name="app_name">ClippingExample</string>
<string name="clipping">Clipping</string>
<string name="translated">translated text</string>
<string name="skewed">"Skewed and "</string>
</resources>
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ClippedView(this))
}
}