1. 程式人生 > 實用技巧 >Paint高階應用---Shader

Paint高階應用---Shader

Paint高階應用篇---Shader

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 拉伸最後一個畫素鋪滿容器。

程式碼如下:

// 獲取Bitmap
private val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.avatar)
private val paint = Paint()

init {
    // 初始化BitmapShader,並將 x,y的拉伸模式設定為CLAMP
    paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawRect(100f,0f,bitmap.width.toFloat() * 2,bitmap.height * 2f,paint)
}

MIRROR

橫向和縱向不足處不斷翻轉映象平鋪。

程式碼如下:

// 獲取Bitmap
private val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.avatar)
private val paint = Paint()

init {
    // 初始化BitmapShader,並將 x,y的拉伸模式設定為MIRROR
    paint.shader = BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawRect(100f,0f,bitmap.width.toFloat() * 2,bitmap.height * 2f,paint)
}

REPEAT

橫向和縱向不足時重複放置,類似於電腦桌布。

程式碼如下:

// 獲取Bitmap
private val bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.avatar)
private val paint = Paint()

init {
    // 初始化BitmapShader,並將 x,y的拉伸模式設定為REPEAT
    paint.shader = BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    canvas.drawRect(100f,0f,bitmap.width * 2f,bitmap.height * 1.5f,paint)
}

線性渲染 LinearGradient

LinearGradient用來實現線性漸變效果。

public class LinearGradient extends Shader {}

漸變效果:

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)
}

使用效果:

// 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)
}
  • 圓形頭像

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)
}
  • 橢圓頭像

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)
}
  • 矩形頭像

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實現頭像

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
    shapeDrawable.bounds = rectF.toRect()
    shapeDrawable.draw(canvas)
}

環形渲染 RadialGradient

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

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

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)
}

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)
}

ps: xiaouwjiang.cn



來自為知筆記(Wiz)