參考:
shader稱為著色器,用來給圖片上色用的;
Shader類只是一個基類,只有兩個方法setLocalMatrix(Matrix localM)
、getLocalMatrix(Matrix localM)
用來設(shè)置坐標(biāo)變換矩陣的欣范;
Shader類與ColorFiler一樣,其實是一個空類弊仪,它的功能的實現(xiàn)熙卡,主要是靠它的派生類來實現(xiàn)的。
BitmapShader
構(gòu)造函數(shù):
public BitmapShader(Bitmap bitmap, TileMode tileX, TileMode tileY)
參數(shù):
- bitmap: 用來指定圖案励饵;
- tileX: 用來指定當(dāng)X軸超出單個圖片大小時時所使用的重復(fù)策略;
- tileY: 同上驳癌,用于指定當(dāng)Y軸超出單個圖片大小時時所使用的重復(fù)策略;取值有:
- TileMode.CLAMP:用邊緣色彩填充多余空間
- TileMode.REPEAT:重復(fù)原圖像來填充多余空間
- TileMode.MIRROR:重復(fù)使用鏡像模式的圖像來填充多余空間
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)
// 在矩形內(nèi)使用指定了shader的畫筆作畫
canvas.drawRect(0f, 0f, width.toFloat(), height * 2 / 3.toFloat(), paint)
}
如上圖,效果:
使用X軸和Y軸都使用REPEAT模式下役听,在超出單個圖像的區(qū)域后颓鲜,就會重復(fù)繪制這個圖像
如上圖表窘,效果:
當(dāng)控件區(qū)域超過當(dāng)前單個圖片的大小時,空白位置的顏色填充就用圖片的邊緣顏色來填充甜滨;
要填充橫向和豎向時乐严,是先填充豎向的!上圖中的右下部分
如上圖衣摩,效果:
鏡相效果其實就是在顯示下一圖片的時候昂验,就相當(dāng)于兩張圖片中間放了一個鏡子一樣;
填充模式混用
Mirror與Repeat混用
// x軸上重復(fù)艾扮,y上鏡像
BitmapShader(bmp, Shader.TileMode.REPEAT, Shader.TileMode.MIRROR)
無論哪兩種模式混合,我們在理解時只需要記著填充順序是
先填充Y軸
泡嘴,然后再填充X軸
甫恩!這樣效果圖就很好理解了;
其他混用也很好理解;
繪圖位置與模式關(guān)系
上面的rect的設(shè)置酌予,都是比較大的磺箕,我們將rect縮小,看看效果
// 矩形小于圖片大小
canvas.drawRect(100f, 100f, 320f, 200f, paint)
如上圖抛虫,可以看到圖片松靡,像是從原圖片上裁剪了一塊,進(jìn)行了繪制建椰;
其實這正說明了一個問題:無論你利用繪圖函數(shù)繪多大一塊击困,在哪繪制,
與Shader無關(guān)
广凸。因為Shader總是在**控件的左上角**開始
,而你繪制的部分只是顯示出來的部分而已蛛枚。沒有繪制的部分雖然已經(jīng)生成谅海,但只是不會顯示出來罷了。
望眼鏡效果
Paint設(shè)置了Shader以后蹦浦,無論我們繪圖位置在哪扭吁,Shader中的圖片都是從控件的左上角開始填充的,而我們所使用的繪圖函數(shù)只是用來指定哪部分顯示出來盲镶,所以當(dāng)我們在手指按下位置畫上一個圓形時侥袜,就會把圓形部分的圖像顯示出來了,看起來就是個望遠(yuǎn)鏡效果溉贿。
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設(shè)置為控件寬高
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生成不規(guī)則頭像
用 xfermode可以實現(xiàn)枫吧,這里采用BitmapShader來實現(xiàn);
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)
}
// 強(qiáng)制控件大小(正方形)
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val size = Math.min(bmp.width, bmp.height)
setMeasuredDimension(size, size)
}
如果是其他形狀宇色,如五角星九杂,通過path來繪制即可颁湖;
LinearGradient線性漸變
構(gòu)造函數(shù)
/**
* (x0, y0), (x1,y1)分別表示開始點與結(jié)束點
* color0 起始點顏色,顏色值必須使用0xAARRGGBB形式的16進(jìn)制表示例隆!表示透明度的AA一定不能少甥捺;
* color1 終點顏色
* tile 填充模式
**/
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
TileMode tile)
/**
* 基本與上類似
* colors[]用于指定漸變的顏色值數(shù)組;
* positions[]與漸變的顏色相對應(yīng)镀层,取值是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
// 減小區(qū)域
canvas.drawRect(100f, 100f, 260f, 200f, paint)
無論哪種Shader闽烙,都是從控件的左上角開始填充的,利用canvas.drawXXX系列函數(shù)只是用來指定顯示哪一塊
文字閃動效果声搁,請參考原博客黑竞,??
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) // 設(shè)置偏移
shade?.setLocalMatrix(matrix)
paint.shader = shade
super.onDraw(canvas)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
anim?.let {
it.cancel()
}
}
RadialGradient 放射漸變
構(gòu)造函數(shù):
// 雙色
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) {
參數(shù)說明:
- centerX,Y: 漸變中心點;
- radius: 漸變半徑疏旨;
- centerColor:漸變起始顏色很魂,取值類型必須是八位的0xAARRGGBB色值!透明底Alpha值不能省略檐涝,不然不會顯示出顏色遏匆。
- edgeColor:結(jié)束顏色,同上谁榜;
- colors 與 stops 與 LinearGradient類似幅聘;stop數(shù)組的起始和終止數(shù)值設(shè)為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()
}