您的位置:首页 > 移动开发 > Android开发

Android中Canvas绘图之Shader使用图文详解

2016-09-02 19:08 495 查看


概述

我们在用Android中的Canvas绘制各种图形时,可以通过
Paint.setShader(shader)
方法为画笔Paint设置shader,这样就可以绘制出多彩的图形。那么Shader是什么呢?做过GPU绘图的同学应该都知道这个词汇,Shader就是着色器的意思。我们可以这样理解,Canvas中的各种drawXXX方法定义了图形的形状,画笔中的Shader则定义了图形的着色、外观,二者结合到一起就决定了最终Canvas绘制的被色彩填充的图形的样子。

android.graphics.Shader
有五个子类,分别是:BitmapShader、LinearGradient、RadialGradient、SweepGradient和ComposeShader,下面依次对这几个类的使用分别说明。


BitmapShader

BitmapShader,顾名思义,就是用Bitmap对绘制的图形进行渲染着色,其实就是用图片对图形进行贴图。

BitmapShader构造函数如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">BitmapShader(Bitmap bitmap, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span> tileX, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span> tileY)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>


第一个参数是Bitmap对象,该Bitmap决定了用什么图片对绘制的图形进行贴图。

第二个参数和第三个参数都是Shader.TileMode类型的枚举值,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。

CLAMP 

CLAMP表示,当所画图形的尺寸大于Bitmap的尺寸的时候,会用Bitmap四边的颜色填充剩余空间。

我们有一个Bitmap,如下所示: 



注意,我们这张图片的四个角是有一定的圆弧的,也就是该Bitmap的四个角点处的像素都是透明的。 

我们使用该Bitmap,演示TileMode为CLAMP的效果,代码如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">BitmapShader bitmapShader = new BitmapShader(bitmap, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(bitmapShader)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmap<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getWidth</span>() * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>, bitmap<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getHeight</span>() * <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


效果如下所示: 


 

我们可以看到,由于我们所绘制的矩形矩形区域比Bitmap大,Bitmap就用右侧边和下侧边的最外层的颜色填充了矩形区域。由于原Bitmap右下角的像素是透明的,所以绘制的矩形的右下角就用透明填充了。

如果我们绘制的图形尺寸小于Bitmap尺寸,那么效果看起来就像是对原Bitmap裁剪了一下而已,代码如下所示: 


 

我们可以看到,当我们所绘制的圆形尺寸小于Bitmap尺寸的时候,看起来的效果就是我们用所绘制的圆形对Bitmap进行了裁剪。

REPEAT 

REPEAT表示,当我们绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域。 

示例代码如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">BitmapShader bitmapShader = new BitmapShader(bitmap, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.REPEAT</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.REPEAT</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(bitmapShader)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getWidth</span>(), canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getHeight</span>(), paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


效果如下所示: 



MIRROR 

与REPEAT类似,当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。 

代码如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">BitmapShader bitmapShader = new BitmapShader(bitmap, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MIRROR</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MIRROR</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(bitmapShader)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getWidth</span>(), canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getHeight</span>(), paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


效果如下所示: 



最后需要说的是,在构造BitmapShader时,tileX和tileY可以取不同的值,二者不用非得一致。


LinearGradient

我们可以用LinearGradient创建线性渐变效果,其有两个构造函数:
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">LinearGradient(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x0, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y0, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x1, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y1, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> color0, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> color1, Shader.TileMode tile)

LinearGradient(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x0, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y0, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> x1, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> y1, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>[] colors, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>[] positions, Shader.TileMode tile)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


我们重点说一下第一个构造函数,在此基础上理解第二个构造函数就很简单了。

LinearGradient是用来创建线性渐变效果的,它是沿着某条直线的方向渐变的,坐标(x0,y0)就是这条渐变直线的起点,坐标(x1,y1)就是这条渐变直线的终点。需要说明的是,坐标(x0,y0)和坐标(x1,y1)都是Canvas绘图坐标系中的坐标。color0和color1分别表示了渐变的起始颜色和终止颜色。与BitmapShader类似,LinearGradient也支持TileMode,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。

使用代码如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">LinearGradient linearGradient = new LinearGradient(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500</span>, Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.GREEN</span>, Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.BLUE</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(linearGradient)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">100</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500</span>, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


效果如下所示: 



上面我们使用了CLAMP,但是由于我们绘制的矩形与渐变位置的大小一样大,所以CLAMP效果不明显。

我们把绘制的区域变大,还是用CLAMP,这次绘制整个Canvas大小的矩形。
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getWidth</span>(), canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getHeight</span>(), paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>


效果如下所示: 



当我们把CLAMP改为REPEAT时,还是绘制整个Canvas大小的矩形,效果如下所示: 



当我们用MIRROR绘制整个Canvas大小的矩形的时候,效果如下所示: 



在LinearGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定positions数组,该数组中每一个position对应colors数组中每个颜色在线段中的相对位置,position取值范围为[0,1],0表示起始位置,1表示终止位置。如果positions数组为null,那么Android会自动为colors设置等间距的位置。


RadialGradient

我们可以用RadialGradient创建从中心向四周发散的辐射渐变效果,其有两个构造函数:
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">RadialGradient(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerX, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerY, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> radius, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> centerColor, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> edgeColor, Shader.TileMode tileMode)

RadialGradient(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerX, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerY, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> radius, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>[] colors, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>[] stops, Shader.TileMode tileMode)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


这两个构造函数和LinearGradient的两个构造函数很类似,我们此处还是重点讲解第一个构造函数,在此基础上理解第二个构造函数就很简单了。

RadialGradient是用来创建从中心向四周发散的辐射渐变效果的,所以我们需要在其构造函数中传入一些圆的参数,坐标(centerX,centerY)是圆心,即起始的中心颜色的位置,radius确定了圆的半径,在圆的半径处的颜色是edgeColor,这样就确定了当位置从圆心移向圆的轮廓时,颜色逐渐从centerColor渐变到edgeColor。RadialGradient也支持TileMode参数,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。

我们首先将CLAMP作为TileMode,代码如下所示:
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> canvasWidth = canvas.getWidth();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> canvasHeight = canvas.getHeight();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerX = canvasWidth / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerY = canvasHeight / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> radius = canvasWidth / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4f</span>;
RadialGradient radialGradient = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> RadialGradient(centerX, centerY, radius, Color.GREEN, Color.BLUE, Shader.TileMode.MIRROR);
paint.setShader(radialGradient);
canvas.drawRect(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, canvasWidth, canvasHeight, paint);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>


效果如下所示: 



在上图中,我们绘制的矩形和Canvas大小一样大,其尺寸大于我们定义的RadialGradient的圆的尺寸。我们可以看到,当使用CLAMP作为TileMode时,颜色从圆心的绿色向圆周的蓝色渐变,在圆以外的空间都用edgeColor蓝色填充。

当我们把CLAMP改为REPEAT时,还是画同样的矩形,效果如下所示: 



我们看到,颜色以绿色到蓝色作为一个渐变周期从圆心向外扩散。

当我们使用MIRROR作为TileMode时,还是画同样的矩形,效果如下所示: 



我们看到,颜色以绿色->蓝色->绿色->蓝色…周期性地交替变换从圆心向外扩散。

在RadialGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定stops数组,该数组中每一个stop对应colors数组中每个颜色在半径中的相对位置,stop取值范围为[0,1],0表示圆心位置,1表示圆周位置。如果stops数组为null,那么Android会自动为colors设置等间距的位置。


SweepGradient

SweepGradient可以用来创建360度颜色旋转渐变效果,具体来说颜色是围绕中心点360度顺时针旋转的,起点就是3点钟位置。

SweepGradient有两个构造函数:
<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">SweepGradient(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> cx, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> cy, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> color0, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> color1)

SweepGradient(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> cx, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> cy, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>[] colors, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>[] positions)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


SweepGradient不支持TileMode参数,我们先讲解第一个构造函数。

坐标(cx,cy)决定了中心点的位置,会绕着该中心点进行360度旋转。color0表示的是起点的颜色位置,color1表示的是终点的颜色位置。

代码如下所示:
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> canvasWidth = canvas.getWidth();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> canvasHeight = canvas.getHeight();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerX = canvasWidth / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerY = canvasHeight / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> radius = canvasWidth / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4f</span>;
SweepGradient sweepGradient = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> SweepGradient(centerX, centerY, Color.GREEN, Color.BLUE);
paint.setShader(sweepGradient);
canvas.drawCircle(centerX, centerY, radius, paint);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>


效果如下所示: 



如上图所示,我们用canvas.drawCircle()方法绘制了一个圆形,将SweepGradient的中心点设置在该圆形的中心,我们可以看到颜色从3点钟位置处的绿色沿着顺时针360度旋转渐变到蓝色。

在SweepGradient的第二个构造函数中,我们可以传入一个colors颜色数组,这样Android就会根据传入的颜色数组一起进行颜色插值。还可以指定positions数组,该数组中每一个position对应colors数组中每个颜色在360度中的相对位置,position取值范围为[0,1],0和1都表示3点钟位置,0.25表示6点钟位置,0.5表示9点钟位置,0.75表示12点钟位置,诸如此类。如果positions数组为null,那么Android会自动为colors设置等间距的位置。

代码如下所示:
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> canvasWidth = canvas.getWidth();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> canvasHeight = canvas.getHeight();
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerX = canvasWidth / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> centerY = canvasHeight / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span> radius = canvasWidth / <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">4f</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>[] colors = {Color.RED, Color.GREEN, Color.BLUE};
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>[] positions = {<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0f</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.5f</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0f</span>};
SweepGradient sweepGradient = <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> SweepGradient(centerX, centerY, colors, positions);
paint.setShader(sweepGradient);
canvas.drawCircle(centerX, centerY, radius, paint);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li></ul>


效果如下所示: 



在上面代码中,我们将红绿蓝三种颜色传入colors数组中,并通过positions数组指定其相对位置分别是0、0.5、1,所以红色是起点颜色,位于3点钟位置;绿色是中间颜色,位于9点钟位置;蓝色是终点颜色,也位于3点钟位置。

当然,起点颜色的位置不一定是0,终点颜色的位置也不一定是1,我们将positions数组改为如下所示:
<code class="hljs cpp has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">float</span>[] positions = {<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.25f</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.5f</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0.75f</span>};</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>


效果如下: 



我们看到颜色的色彩比例发生变化。起始颜色红色的位置是0.25不是0,但是从3点钟位置起颜色就是红色。与其不同的是终止颜色蓝色,蓝色的位置是0.75不是1,其对应12点钟位置,从12点钟到3点钟这90度的空间都是透明的,没有被颜色填充,在使用时大家注意。

如果我们在此基础上绘制整个Canvas大小的矩形,效果如下所示: 




ComposeShader

ComposeShader,顾名思义,就是混合Shader的意思,它可以将两个Shader按照一定的Xfermode组合起来。

ComposeShader有两个构造函数,如下所示:
<code class="hljs mizar has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">ComposeShader(Shader shaderA, Shader shaderB, Xfermode <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">mode</span>)

ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">mode</span>)</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


如果对Xfermode不熟悉的话,强烈建议您先读一下我的另一篇博文《Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解》

此处对Xfermode做一下简单介绍,Xfermode可以用于实现新绘制的像素与Canvas上对应位置已有的像素按照混合规则进行颜色混合。Xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,其中前两个类现在被Android废弃了,现在主要用的是PorterDuffXfermode。PorterDuffXfermode的构造函数需要指定PorterDuff.Mode的类型。所以,上面的第二个构造函数可以看做是第一个构造函数的特例。我们主要讲解第二个,二者大同小异。

我们知道,在使用Xfermode的时候,存在目标像素DST和源像素SRC之说。源像素指的是将要向Canvas上绘制的像素,目标像素指的是源像素在Canvas上对应位置已经存在的像素。

构造函数中的shaderA对应着目标像素,shaderB对应着源像素。

有一点需要说明,ComposeShader这个类不是必须的,也就是我们不用这个类也能创造对应的效果,它类似于一个助手类,为我们实现某种效果提供了方便,下面举例说明。

我们有如下透明图片: 



上面的图片是透明的,不过图片中有个心形图案是白色,不透明。 

我想让渐变颜色只填充上图中的❤形区域,透明部分不填充,颜色从绿色渐变到蓝色,渐变方向从左上角到右下角。我们不用ComposeShader即可实现上述效果,代码如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">int bitmapWidth = bitmap<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getWidth</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
int bitmapHeight = bitmap<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getHeight</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//将绘制代码放入到canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.saveLayer</span>()和canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.restore</span>()之间
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.saveLayer</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmapWidth, bitmapHeight, null, Canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.ALL</span>_SAVE_FLAG)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//创建BitmapShader,用以绘制❤形
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//将BitmapShader作为画笔paint绘图所使用的shader
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(bitmapShader)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//用BitmapShader绘制矩形
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmapWidth, bitmapHeight, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//将画笔的Xfermode设置为PorterDuff<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Mode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MULTIPLY</span>模式
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setXfermode</span>(new PorterDuffXfermode(PorterDuff<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Mode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MULTIPLY</span>))<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
LinearGradient linearGradient = new LinearGradient(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmapWidth, bitmapHeight, Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.GREEN</span>, Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.BLUE</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//将创建LinearGradient作为画笔paint绘图所使用的shader
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(linearGradient)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//用LinearGradient绘制矩形
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmapWidth, bitmapHeight, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//最后将画笔去除掉Xfermode
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setXfermode</span>(null)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.restore</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li></ul>


效果如下所示: 



如果认真读过博文《Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解》的话,我相信大家应该能明白上图出现的原因。

此处我们还是一起分析一下代码的执行过程。

我们的图片中间的❤形区域是纯白色,该区域的像素颜色值ARGB分量是(255,255,255,255)。❤形区域以外的区域是纯透明的,该区域的像素颜色值ARGB分量是(0,0,0,0)。

为了使用Xfermode,我们将绘图的代码放到了canvas.saveLayer()和canvas.restore()之间,对此有疑问的同学可以参见我上述提到的博文。canvas.saveLayer()会创建一个新的绘图图层,而且该图层是全透明的,我们后面的代码都是绘制到这个图层上,而不是直接绘制到Canvas上。

我们用上述Bitmap创建了一个BitmapShader,并将其绑定到画笔Paint中。当我们用canvas.drawRect()绘制矩形时,就会用该BitmapShader填充,此时的效果应该是在新创建的layer上绘制了一个白色的心形。

然后我们创建了一个PorterDuffXfermode的实例,并通过paint.setXfermode()将其绑定到画笔paint上。其中PorterDuffXfermode的mode类型为MULTIPLY。MULTIPLY的意思是将源像素的ARGB四个分量分别与目标像素对应的ARGB四个分量相乘,将相乘的结果作为混合后的像素。此处进行相乘时,ARGB四个分量都已经从[0, 255]的区间归一化到[0.0, 1.0]的区间。

然后我们创建了一个LinearGradient,用以实现颜色线性渐变效果。颜色从左上角的绿色渐变到右下角的蓝色。然后我们通过paint.setShader()方法将其绑定到画笔paint的shader上。

后面我们再次调用canvas.drawRect()绘制同样大小的一个矩形。在绘制时,我们的画笔已经同时绑定了Xfermode和Shader。首先canvas会用LinearGradient绘制一个具有渐变色的矩形区域。然后根据画笔设置的PorterDuff.Mode.MULTIPLY类型,将那些由渐变色填充的矩形区域中的像素与我们在第3步中绘制的心形图片中的像素颜色进行相乘混合。渐变色填充的矩形区域中的像素是源像素,第3步中绘制的心形图片中的像素是目标像素。目标像素中❤形区域是纯白色的,其像素颜色是(255,255,255,255),归一化后的颜色是(1,1,1,1),对应位置的源像素中的ARGB颜色分量与其相乘,最终的颜色还是源像素的颜色,即心形区域被源像素着上了渐变色。目标像素中❤形区域以外的颜色是纯透明的,颜色是(0,0,0,0),对应位置的源像素中的ARGB颜色分量与其相乘,最终的颜色还是目标像素中的(0,0,0,0),即心形区域以外没有被着色,依旧呈现透明色。

最后通过调用canvas.restore()方法将新创建的layer绘制到Canvas上去,这样我们就看到最终的效果了。

下面我们看看如和用ComposeShader实现上述效果,代码如下所示:
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">int bitmapWidth = bitmap<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getWidth</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
int bitmapHeight = bitmap<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.getHeight</span>()<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//创建BitmapShader,用以绘制❤形
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
LinearGradient linearGradient = new LinearGradient(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmapWidth, bitmapHeight, Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.GREEN</span>, Color<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.BLUE</span>, Shader<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.TileMode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.CLAMP</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.Mode</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.MULTIPLY</span>)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//将组合的composeShader作为画笔paint绘图所使用的shader
paint<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.setShader</span>(composeShader)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span>
//用composeShader绘制矩形区域
canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawRect</span>(<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>, bitmapWidth, bitmapHeight, paint)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li></ul>


用ComposeShader实现的效果与上图相同,我就不再贴图了。我们可以看到,使用ComposeShader之后,实现相同的效果时,代码量明显减少了,而且我们也不需要将绘图代码放到canvas.saveLayer()和canvas.restore()之间了。

根据上面的示例,我们可以得出如下结论: 

假设我们定义了两个Shader的变量,shaderA和shaderB,并分别对这两个Shader进行了实例化。 

可以使用ComposeShader将二者组合使用,基本代码如下所示:
<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">ComposeShader composeShader = new ComposeShader(shaderA, shaderB, porterDuffMode);
paint.setShader(composeShader);
canvas.drawXXX(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>, paint);</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li></ul>


上述代码等价于下面的代码片段:
<code class="hljs r has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: "Source Code Pro", monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
paint.setShader(shaderA);
canvas.drawXXX(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>, paint);
paint.setXfermode(new PorterDuffXfermode(mode));
paint.setShader(shaderB);
canvas.drawXXX(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">...</span>, paint);
paint.setXfermode(null);
canvas.restore();</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li></ul>


此处所说的以上两个代码片段等价的前提是,两个代码片段中的canvas.drawXXX(…, paint)方法中调用的drawXXX方法相同,并且里面传入的参数都相同,例如我们之前两段心形代码示例中都调用drawRect()方法且绘制的矩形的位置及尺寸都相同。


总结

本文依次介绍了Shader的五个子类:BitmapShader、LinearGradient、RadialGradient、SweepGradient和ComposeShader。并在最后对ComposeShader这个相对复杂的示例进行了讲解,如果大家能看明白最后ComposeShader这个示例,相信大家已经对Shader理解地比较透彻了。

关于LinearGradient、RadialGradient、SweepGradient这三个渐变效果Shader,大家也可以参考一下博文《图文详解Andorid中用Shape定义GradientDrawable》,该文详细介绍了如何用XML中的
<shape>
节点定义各种具有渐变效果的GradientDrawable,这两篇博文可互为映照。

如果觉得文章还可以,点击下面帮我顶一下,希望本文对大家使用Shader进行绘图有所帮助!

相关阅读: 
我的Android博文整理汇总 
Android中Canvas绘图基础详解(附源码下载) 
Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解 
图文详解Andorid中用Shape定义GradientDrawable 
Android中Canvas绘图之MaskFilter图文详解(附源码下载)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: