Paint的高级用法主要有:渲染着色滤镜Xfermode

CanvasdrawXXX方法画具体的形状,PaintShader定义图像的着色外观

目前 Shader的子类有 LinearGradient(线性渲染)SweepGradient(渐变渲染/梯度渲染)
RadialGradient(环形渲染)BitmapShader(位图渲染)ComposeShader(组合渲染)

TileMode (拉伸形式)

TileMode 拉伸方式,在Shader的创建中会用到。只在图片和显示区域大小不符的情况下进行扩充渲染TileMode源码定义如下:

public enum TileMode {
   /**
    * replicate the edge color if the shader draws outside of its
    * original bounds
    */
    CLAMP   (0),
   /**
    * repeat the shader's image horizontally and vertically
    */
    REPEAT  (1),
   /**
    * repeat the shader's image horizontally and vertically, alternating
    * mirror images so that adjacent images always seam
    */
    MIRROR  (2);
}

从源码定义上可以看到TileMode是一个枚举类,定义了3种类型:CLAMPREPEATMIRROR

CLAMP

CLAMP 拉伸最后一个像素铺满容器。

image-20210120120028712

代码如下:

// 获取Bitmap
private val bitmap = BitmapFactory.decodeResource(resources, R.drawable.avator)

private val paint = Paint().apply {
    isAntiAlias = true
    isDither = true
    // 设置路径效果,圆角
    pathEffect = CornerPathEffect(25f)
    shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.translate(width / 4f, height / 4f)
    canvas.drawRect(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat(), paint)
    canvas.translate(0f, bitmap.width.toFloat())
    canvas.drawRect(-50f, 0f, bitmap.width.toFloat() + 100, bitmap.height.toFloat() + 100, paint)
}

MIRROR

横向和纵向不足处不断翻转镜像平铺。

image-20210120133425081

代码同CLAMP,只需将shader改一下即可:

shader = BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)

REPEAT

横向和纵向不足时重复放置,类似于电脑壁纸。

image-20210120133738294

代码同CLAMP,只需将shader改一下即可:

shader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)

线性渲染 LinearGradient

LinearGradient用来实现线性渐变效果。

public class LinearGradient extends Shader {}

渐变效果:

image-20201210174046124
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val linearGradient = LinearGradient(0f, 0f, 500f, 300f, Color.RED, Color.BLUE, Shader.TileMode.CLAMP)
    paint.shader = linearGradient
    canvas.drawRect(0f, 0f, 800f, 800f, paint)
}

使用效果:

image-20201210162543735
// 0x22ffffff, -0x1, 0x22ffffff
class LinearGradientText @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
    // 线性渐变
    private var linearGradient: LinearGradient? = null
    // 平移matrix
    private val translateMatrix: Matrix = Matrix()
    // 平移变量默认值
    private var defaultX: Float = 20f
    // 平移量间隔值
    private var translateX: Float = 0f
 
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        // 获取文本值
        val text = text.toString()
        // 获取文本的测量宽度
        val measureTextWidth = paint.measureText(text)
        val gradientSize = measureTextWidth / text.length * 3
        linearGradient = LinearGradient(
            -gradientSize, 0f, 0f, 0f,
            intArrayOf(0x22ffffff, -0x1, 0x22ffffff), null, Shader.TileMode.CLAMP
        )
        paint.shader = linearGradient
    }
 
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        translateX += defaultX
        val measureTextWidth = paint.measureText(text.toString())
        val count = if (lineCount > 1) lineCount - 1 else 1
        if (translateX > measureTextWidth / count || translateX < 1) {
            defaultX = -defaultX
        }
        translateMatrix.setTranslate(translateX, 0f)
        linearGradient?.setLocalMatrix(translateMatrix)
        postInvalidateDelayed(50)
    }
}

位图渲染 BitmapShader

BitmapShader 位图图像渲染,用Bitmap对绘制的图形进行渲染着色,简单来说是用图片对图形进行贴图。

TileMode中已有基本使用,具体请看 CLAMP

以下使用代码公共部分:

class GradientView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
 
    private val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.avatar)
    private var bWidth: Int = 0
    private var bHeight: Int = 0
    private val paint = Paint()
    private var bitmapShader: BitmapShader? = null
    private val shapeMatrix = Matrix()
    private val rectF = RectF()
 
    init {
        // 获取资源图片
        bWidth = bitmap.width
        bHeight = bitmap.height
        /**
         * TileMode.CLAMP 拉伸最后一个像素去铺满剩下的地方
         * TileMode.MIRROR 通过镜像翻转铺满剩下的地方。
         * TileMode.REPEAT 重复图片平铺整个画面(电脑设置壁纸)
         * 在图片和显示区域大小不符的情况进行扩充渲染
         */
        bitmapShader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
        paint.shader = bitmapShader
    }
 
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
}
  • 圆形头像
image-20201210164831234
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 坐标轴移动1/4的原因是,坐标轴移动1/4的位置为原点,然后将图片缩放后,图片的顶点与原点重合
    // 进行画园时刚好位于正中心,切换切割的图片也是图片的y中心部分
    canvas.translate(width / 4f, height / 4f)
    paint.shader = bitmapShader
    paint.isAntiAlias = true
    // 图片缩放
    val scale = max(bWidth, bHeight) / min(bWidth, bHeight) * 1.0f
    shapeMatrix.setScale(scale, scale)
    shader.setLocalMatrix(shapeMatrix)
    canvas.drawCircle(bHeight / 2f, bHeight / 2f, bHeight / 2f, paint)
}
  • 椭圆头像
image-20201210171000171
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 坐标轴移动1/4的原因是,坐标轴移动1/4的位置为原点,然后将图片缩放后,图片的顶点与原点重合
    // 进行画园时刚好位于正中心,切换切割的图片也是图片的y中心部分
    canvas.translate(width / 4f, height / 4f)
    paint.shader = bitmapShader
    paint.isAntiAlias = true
    // 图片缩放
    val scale = max(bWidth, bHeight) / min(bWidth, bHeight) * 1.0f
    shapeMatrix.setScale(scale, scale)
    shader.setLocalMatrix(shapeMatrix)
    rectF.set(0f, 0f, bWidth + 100f, bHeight.toFloat())
    canvas.drawOval(rectF, paint)
}
  • 矩形头像
image-20201210171452728
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
       canvas.translate(width/4, height/4)
    paint.shader = bitmapShader
    // 抗锯齿
    paint.isAntiAlis = true
    val scale = max(bWidth, bHeight) / min(bWidth, bHeight)
    shapeMatrix.setScale(scale,scale)
    paint.shader.setLocalMatrix(shapeMatrix)
    rectF.set(0f, 0f, bWidth.toFloat(), bHeight.toFloat())
    canvas.drawRect(rectF,paint)
}
  • ShapeDrawable实现头像
image-20201210164831234
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.translate(width / 4f, height / 4f)
    paint.shader = bitmapShader
    paint.isAntiAlias = true
    // 图片缩放
    val scale = max(bWidth, bHeight) / min(bWidth, bHeight)
    shapeMatrix.setScale(scale, scale)
    paint.shader.setLocalMatrix(shapeMatrix)
    rectF.set(0f, 0f, bWidth.toFloat(), bHeight.toFloat())
    val shapeDrawable = ShapeDrawable(OvalShape())
    shapeDrawable.paint.shader = bitmapShader
    // 一定要设置bounds,不然可能不显示
    shapeDrawable.bounds = rectF.toRect()
    shapeDrawable.draw(canvas)
}

环形渲染 RadialGradient

image-20201210180214610
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val radialGradient = RadialGradient(
        400f, 400f, 300f,
        intArrayOf(
            Color.RED,
            Color.GREEN,
            Color.BLUE,
            Color.YELLOW
        ), null, Shader.TileMode.REPEAT
    )
    paint.shader = radialGradient
    canvas.drawCircle(400f,400f,400f, paint)
}

渐变渲染 SweepGradient

image-20201211093531255
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val sweepGradient = SweepGradient(
        300f,300f,
        intArrayOf(
            Color.RED,
            Color.GREEN,
            Color.BLUE,
            Color.YELLOW
    ), null)
    paint.shader = sweepGradient
    canvas.drawCircle(300f,300f,300f,paint)
}

组合着色 ComposeShader

image-20201211093902530
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val linearGradient = LinearGradient(0f, 0f, 300f, 300f, Color.RED, Color.WHITE,Shader.TileMode.CLAMP)
    val sweepGradient = SweepGradient(
        200f, 200f, intArrayOf(
            Color.RED,
            Color.GREEN,
            Color.BLUE,
            Color.YELLOW
        ), null
    )
    val composeShader = ComposeShader(linearGradient,sweepGradient,PorterDuff.Mode.MULTIPLY)
    paint.shader = composeShader
    canvas.drawRect(100f,100f,500f,500f,paint)
}
image-20201211094327261
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.heart)
    canvas.translate(measuredWidth/4f, measuredHeight/4f)
    val heartBitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
 
    val linearGradient = LinearGradient(
        0f,
        0f,
        bitmap.width.toFloat(),
        bitmap.height.toFloat(),
        Color.RED,
        Color.GREEN,
        Shader.TileMode.CLAMP
    )
    val composeShader =
    ComposeShader(heartBitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY)
    paint.isAntiAlias = true
    paint.shader = composeShader
    canvas.drawRect(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat(), paint)
}

备注:
在进行各种渲染操作时,或多或少会用到矩阵Matrix的相关操作,那么prepostset的区别如下:

pre是在队列最前面插入,post是在队列最后面追加,而set先清空队列在添加。