Paint高階應用---Shader
阿新 • • 發佈:2020-12-15
Paint高階應用篇---Shader
Paint的高階用法主要有:渲染著色、濾鏡 和 Xfermode。
Canvas
的drawXXX
方法畫具體的形狀,Paint
的Shader
定義影象的著色和外觀。
目前 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種類型:CLAMP
、REPEAT
和 MIRROR
。
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)