Paint高级应用---Shader
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