Android 自定义View 实例2_Clipping Canvas

上一篇 Android 自定义View 实例_ 画图  参考: https://blog.csdn.net/whjk20/article/details/115639448

这里是Canvas 的裁剪 (同时可以控制可见性, 主要是考虑到绘制的性能)

目录

1. 相关概念

(1)Render 渲染

(2)Clipping 裁剪 (只绘制可见部分,提升性能)

(3)overdraw 过度绘制 (减少过度绘制,提升性能)

2. 屏幕分辩率

3. The Drawing Algorithm

4. Clipping methods

5. 实例

5.1 效果图:

5.2 代码


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

 

  1.  canvas.save()  保存当前状态
  2. canvas.translate() 移动到特定的坐标
  3. canvas.clipRect()/clipOutRect  相对当前画布,不进行绘制的部分(裁剪部分) 
  4.  draw
  5.  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))
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值