参考:
- https://blog.csdn.net/harvic880925/article/details/52039081
shader称为着色器,用来给图片上色用的;
Shader类只是一个基类,只有两个方法setLocalMatrix(Matrix localM)
、getLocalMatrix(Matrix localM)
用来设置坐标变换矩阵的;
Shader类与ColorFiler一样,其实是一个空类,它的功能的实现,主要是靠它的派生类来实现的。
BitmapShader
构造函数:
public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)
参数:
- bitmap: 用来指定图案;
- tileX: 用来指定当X轴超出单个图片大小时时所使用的重复策略;
- tileY: 同上,用于指定当Y轴超出单个图片大小时时所使用的重复策略;取值有:
- TileMode.CLAMP:用边缘色彩填充多余空间
- TileMode.REPEAT:重复原图像来填充多余空间
- TileMode.MIRROR:重复使用镜像模式的图像来填充多余空间
val bmp = BitmapFactory.decodeResource(resources, R.mipmap.sanjing)
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
shader = BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 在矩形内使用指定了shader的画笔作画
canvas.drawRect(0f, 0f, width.toFloat(), height * 2 / 3.toFloat(), paint)
}
如上图,效果:
使用X轴和Y轴都使用REPEAT模式下,在超出单个图像的区域后,就会重复绘制这个图像
如上图,效果:
当控件区域超过当前单个图片的大小时,空白位置的颜色填充就用图片的边缘颜色来填充;
要填充横向和竖向时,是先填充竖向的!上图中的右下部分
如上图,效果:
镜相效果其实就是在显示下一图片的时候,就相当于两张图片中间放了一个镜子一样;
填充模式混用
Mirror与Repeat混用
// x轴上重复,y上镜像
BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR)
无论哪两种模式混合,我们在理解时只需要记着填充顺序是
先填充Y轴
,然后再填充X轴
!这样效果图就很好理解了;
其他混用也很好理解;
绘图位置与模式关系
上面的rect的设置,都是比较大的,我们将rect缩小,看看效果
// 矩形小于图片大小
canvas.drawRect(100f, 100f, 320f, 200f, paint)
如上图,可以看到图片,像是从原图片上裁剪了一块,进行了绘制;
其实这正说明了一个问题:无论你利用绘图函数绘多大一块,在哪绘制,
与Shader无关
。因为Shader总是在**控件的左上角**开始
,而你绘制的部分只是显示出来的部分而已。没有绘制的部分虽然已经生成,但只是不会显示出来罢了。
望眼镜效果
Paint设置了Shader以后,无论我们绘图位置在哪,Shader中的图片都是从控件的左上角开始填充的,而我们所使用的绘图函数只是用来指定哪部分显示出来,所以当我们在手指按下位置画上一个圆形时,就会把圆形部分的图像显示出来了,看起来就是个望远镜效果。
val bmp = BitmapFactory.decodeResource(resources, R.mipmap.animal_)
var bmpBG: Bitmap? = null
var dx = -1f
var dy = -1f
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
if (bmpBG == null) {
// bitmap设置为控件宽高
bmpBG = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvasBG = Canvas(bmpBG)
canvasBG.drawBitmap(bmp, null, RectF(0f, 0f, width * 1.0f, height * 1.0f), paint)
}
// 画出局部
if (dx != -1f && dy != -1f) {
paint.shader = BitmapShader(bmpBG, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
canvas.drawCircle(dx, dy, 150f, paint)
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN,
MotionEvent.ACTION_MOVE -> {
dx = event.x
dy = event.y
}
else -> {
dx = -1f
dy = -1f
}
}
postInvalidate()
return true
}
BitmapShader生成不规则头像
用 xfermode可以实现,这里采用BitmapShader来实现;
val bmp = BitmapFactory.decodeResource(resources, R.mipmap.sanjing)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
val shader = BitmapShader(bmp, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val matrix = Matrix()
val size = Math.min(bmp.width, bmp.height)
val scale = size / Math.min(width, height).toFloat()
matrix.setScale(scale, scale) // Matrix 缩放
shader.setLocalMatrix(matrix)
paint.shader = shader
canvas.drawCircle(size / 2.toFloat(), size / 2.toFloat(),
size / 2.toFloat(), paint)
}
// 强制控件大小(正方形)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val size = Math.min(bmp.width, bmp.height)
setMeasuredDimension(size, size)
}
如果是其他形状,如五角星,通过path来绘制即可;
LinearGradient线性渐变
构造函数
/**
* (x0, y0), (x1,y1)分别表示开始点与结束点
* color0 起始点颜色,颜色值必须使用0xAARRGGBB形式的16进制表示!表示透明度的AA一定不能少;
* color1 终点颜色
* tile 填充模式
**/
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
TileMode tile)
/**
* 基本与上类似
* colors[]用于指定渐变的颜色值数组;
* positions[]与渐变的颜色相对应,取值是0-1的float类型,表示在每一个颜色在整条渐 变线中的百分比位置
**/
public LinearGradient(float x0, float y0, float x1, float y1, int colors[],
float positions[], TileMode tile)
双色渐变示例:
paint.shader = LinearGradient(0f, (height / 2).toFloat(), width.toFloat(),
(height / 2).toFloat(), -0x10000, -0xff0100, Shader.TileMode.CLAMP)
canvas.drawRect(0f, 0f, width * 1.0f, height * 1.0f, paint)
多色渐变示例:
val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100, -0xff0001)
val pos = floatArrayOf(0f, 0.2f, 0.4f, 0.6f, 1.0f) // 20%,20%,20%,20%,40%
val multiGradient = LinearGradient(0f, (height / 2).toFloat(), width.toFloat(),
(height / 2).toFloat(), colors, pos, Shader.TileMode.CLAMP)
paint.shader = multiGradient
canvas.drawRect(0f, 0f, width * 1.0f, height * 1.0f, paint)
??如上图,这里的 变线中的百分比位置不太理解
填充模式
上面都是 clamp
边缘填充,我们使用下repeat
填充,渐变点是从(0,0)到屏幕的中间点(width/2,height.2):
val multiGradient = LinearGradient(0f, 0f,
width / 2.toFloat(), height / 2.toFloat(),
colors, pos, Shader.TileMode.REPEAT)
Mirror很容易理解;
填充方式是什么?
类似bitmapShader
也是从控件左上角开始填充;
// 多个颜色
val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100, -0xff0001)
val pos = floatArrayOf(0f, 0.2f, 0.4f, 0.6f, 1.0f) // 20%,20%,20%,20%,40%
val multiGradient = LinearGradient(0f, 0f, width / 2.toFloat(), height / 2.toFloat(), colors, pos, Shader.TileMode.REPEAT)
paint.shader = multiGradient
// 减小区域
canvas.drawRect(100f, 100f, 260f, 200f, paint)
无论哪种Shader,都是从控件的左上角开始填充的,利用canvas.drawXXX系列函数只是用来指定显示哪一块
文字闪动效果,请参考原博客,
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
}
private var shade: LinearGradient? = null
private var mDx = 0f
private var anim: ValueAnimator? = null
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
if (shade == null) {
shade = LinearGradient((-measuredWidth).toFloat(), 0f, 0f, 0f,
intArrayOf(currentTextColor, -0xff0100, currentTextColor),
floatArrayOf(0f, 0.5f, 1f),
Shader.TileMode.CLAMP
)
anim = ValueAnimator.ofFloat(0f, 2 * measuredWidth * 1.0f).apply {
duration = 1500
repeatMode = ValueAnimator.RESTART
repeatCount = ValueAnimator.INFINITE
addUpdateListener { it ->
mDx = it.animatedValue as Float
postInvalidate()
}
}
anim?.start()
}
}
override fun onDraw(canvas: Canvas?) {
val matrix = Matrix()
matrix.setTranslate(mDx, 0f) // 设置偏移
shade?.setLocalMatrix(matrix)
paint.shader = shade
super.onDraw(canvas)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
anim?.let {
it.cancel()
}
}
RadialGradient 放射渐变
构造函数:
// 双色
public RadialGradient(float centerX, float centerY, float radius,
int centerColor, int edgeColor,TileMode tileMode)
// 多色
public RadialGradient(float centerX, float centerY, float radius,
int colors[], float stops[],
TileMode tileMode) {
参数说明:
- centerX,Y: 渐变中心点;
- radius: 渐变半径;
- centerColor:渐变起始颜色,取值类型必须是八位的0xAARRGGBB色值!透明底Alpha值不能省略,不然不会显示出颜色。
- edgeColor:结束颜色,同上;
- colors 与 stops 与 LinearGradient类似;stop数组的起始和终止数值设为0和1;
示例
private var shade: RadialGradient? = null
val paint = Paint().apply {}
val radius = 400f
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 双色
// shade = RadialGradient(width / 2.toFloat(), height / 2.toFloat(),
// 100f, 0xffff0000.toInt(), 0xff00ff00.toInt(), Shader.TileMode.REPEAT)
// 多色
val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100)
val stops = floatArrayOf(0f, 0.2f, 0.5f, 1f)
shade = RadialGradient(width / 2.toFloat(), height / 2.toFloat(),
radius, colors, stops, Shader.TileMode.REPEAT)
paint.shader = shade
canvas.drawCircle(width / 2.toFloat(), height / 2.toFloat(),
radius, paint)
}
填充模式
比较好理解;
填充方式
shader都是从控件的左上角开始填充的;不信,就看
val colors = intArrayOf(-0x10000, -0xff0100, -0xffff01, -0x100)
val stops = floatArrayOf(0f, 0.2f, 0.5f, 1f)
shade = RadialGradient(width / 2.toFloat(), height / 2.toFloat(),
100f, colors, stops, Shader.TileMode.REPEAT)
paint.shader = shade
canvas.drawCircle(200f, 200f,150f, paint)
水波纹按钮效果
比较好理解,如下代码:
init {
setLayerType(LAYER_TYPE_SOFTWARE, null)
}
private var shade: RadialGradient? = null
val paint = Paint().apply {}
var currentX: Float = 0f
var currentY: Float = 0f
val DEFAULT_RADIUS = 80f
var radius = 0f
set(value) {
field = value
if (value > 0) {
shade = RadialGradient(currentX, currentY, value,
0x00ffffff, 0xFF58FAAC.toInt(), Shader.TileMode.CLAMP)
paint.shader = shade
}
postInvalidate()
}
var anim: Animator? = null
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(currentX, currentY, radius, paint)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
currentX = event.x
currentY = event.y
radius = DEFAULT_RADIUS
return true
}
MotionEvent.ACTION_UP -> {
startAnim(400)
}
}
return super.onTouchEvent(event)
}
private fun startAnim(duration: Long) {
anim?.let { it.cancel() }
anim = ObjectAnimator.ofFloat(this, "radius", DEFAULT_RADIUS, width * 1.0f).apply {
setDuration(duration)
interpolator = AccelerateInterpolator()
addListener(object : Animator.AnimatorListener {
override fun onAnimationRepeat(animation: Animator) {
}
override fun onAnimationEnd(animation: Animator) {
radius = 0f // 清除效果
}
override fun onAnimationCancel(animation: Animator) {
}
override fun onAnimationStart(animation: Animator) {
}
})
}
anim?.start()
}