当我们把CLAMP改为REPEAT时,还是画同样的矩形,效果如下所示: 我们看到,颜色以绿色到蓝色作为一个渐变周期从圆心向外扩散。 当我们使用MIRROR作为TileMode时,还是画同样的矩形,效果如下所示: 我们看到,颜色以绿色->蓝色->绿色->蓝色…周期性地交替变换从圆心向外扩散。 在RadialGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定stops数组,该数组中每一个stop对应colors数组中每个颜色在半径中的相对位置,stop取值范围为[0,1],0表示圆心位置,1表示圆周位置。如果stops数组为null,那么Android会自动为colors设置等间距的位置。 SweepGradientSweepGradient可以用来创建360度颜色旋转渐变效果,具体来说颜色是围绕中心点360度顺时针旋转的,起点就是3点钟位置。 SweepGradient有两个构造函数: SweepGradient(float cx, float cy, int color0, int color1) SweepGradient(float cx, float cy, int[] colors, float[] positions) SweepGradient不支持TileMode参数,我们先讲解第一个构造函数。 坐标(cx,cy)决定了中心点的位置,会绕着该中心点进行360度旋转。color0表示的是起点的颜色位置,color1表示的是终点的颜色位置。 代码如下所示: int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); float centerX = canvasWidth / 2f; float centerY = canvasHeight / 2f; float radius = canvasWidth / 4f; SweepGradient sweepGradient = new SweepGradient(centerX, centerY, Color.GREEN, Color.BLUE); paint.setShader(sweepGradient); canvas.drawCircle(centerX, centerY, radius, paint); 效果如下所示: 如上图所示,我们用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设置等间距的位置。 代码如下所示: int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); float centerX = canvasWidth / 2f; float centerY = canvasHeight / 2f; float radius = canvasWidth / 4f; int[] colors = {Color.RED, Color.GREEN, Color.BLUE}; float[] positions = {0f, 0.5f, 0f}; SweepGradient sweepGradient = new SweepGradient(centerX, centerY, colors, positions); paint.setShader(sweepGradient); canvas.drawCircle(centerX, centerY, radius, paint); 效果如下所示: 在上面代码中,我们将红绿蓝三种颜色传入colors数组中,并通过positions数组指定其相对位置分别是0、0.5、1,所以红色是起点颜色,位于3点钟位置;绿色是中间颜色,位于9点钟位置;蓝色是终点颜色,也位于3点钟位置。 当然,起点颜色的位置不一定是0,终点颜色的位置也不一定是1,我们将positions数组改为如下所示: float[] positions = {0.25f, 0.5f, 0.75f}; 效果如下: 我们看到颜色的色彩比例发生变化。起始颜色红色的位置是0.25不是0,但是从3点钟位置起颜色就是红色。与其不同的是终止颜色蓝色,蓝色的位置是0.75不是1,其对应12点钟位置,从12点钟到3点钟这90度的空间都是透明的,没有被颜色填充,在使用时大家注意。 如果我们在此基础上绘制整个Canvas大小的矩形,效果如下所示: ComposeShaderComposeShader,顾名思义,就是混合Shader的意思,它可以将两个Shader按照一定的Xfermode组合起来。 ComposeShader有两个构造函数,如下所示: ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode) ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode) 如果对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即可实现上述效果,代码如下所示: int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); //将绘制代码放入到canvas.saveLayer()和canvas.restore()之间 canvas.saveLayer(0, 0, bitmapWidth, bitmapHeight, null, Canvas.ALL_SAVE_FLAG); //创建BitmapShader,用以绘制❤形 BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); //将BitmapShader作为画笔paint绘图所使用的shader paint.setShader(bitmapShader); //用BitmapShader绘制矩形 canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint); //将画笔的Xfermode设置为PorterDuff.Mode.MULTIPLY模式 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY)); //创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果 LinearGradient linearGradient = new LinearGradient(0, 0, bitmapWidth, bitmapHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP); //将创建LinearGradient作为画笔paint绘图所使用的shader paint.setShader(linearGradient); //用LinearGradient绘制矩形 canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint); //最后将画笔去除掉Xfermode paint.setXfermode(null); canvas.restore(); 效果如下所示: 如果认真读过博文 《Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解》 的话,我相信大家应该能明白上图出现的原因。 此处我们还是一起分析一下代码的执行过程。
下面我们看看如和用ComposeShader实现上述效果,代码如下所示: int bitmapWidth = bitmap.getWidth(); int bitmapHeight = bitmap.getHeight(); //创建BitmapShader,用以绘制❤形 BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); //创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果 LinearGradient linearGradient = new LinearGradient(0, 0, bitmapWidth, bitmapHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP); //bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式 ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY); //将组合的composeShader作为画笔paint绘图所使用的shader paint.setShader(composeShader); //用composeShader绘制矩形区域 canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint); 用ComposeShader实现的效果与上图相同,我就不再贴图了。我们可以看到,使用ComposeShader之后,实现相同的效果时,代码量明显减少了,而且我们也不需要将绘图代码放到canvas.saveLayer()和canvas.restore()之间了。 根据上面的示例,我们可以得出如下结论: 假设我们定义了两个Shader的变量,shaderA和shaderB,并分别对这两个Shader进行了实例化。 可以使用ComposeShader将二者组合使用,基本代码如下所示: ComposeShader composeShader = new ComposeShader(shaderA, shaderB, porterDuffMode); paint.setShader(composeShader); canvas.drawXXX(..., paint); 上述代码等价于下面的代码片段: canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG); paint.setShader(shaderA); canvas.drawXXX(..., paint); paint.setXfermode(new PorterDuffXfermode(mode)); paint.setShader(shaderB); canvas.drawXXX(..., paint); paint.setXfermode(null); canvas.restore(); 此处所说的以上两个代码片段等价的前提是,两个代码片段中的canvas.drawXXX(…, paint)方法中调用的drawXXX方法相同,并且里面传入的参数都相同,例如我们之前两段心形代码示例中都调用drawRect()方法且绘制的矩形的位置及尺寸都相同。 总结本文依次介绍了Shader的五个子类:BitmapShader、LinearGradient、RadialGradient、SweepGradient和ComposeShader。并在最后对ComposeShader这个相对复杂的示例进行了讲解,如果大家能看明白最后ComposeShader这个示例,相信大家已经对Shader理解地比较透彻了。 关于LinearGradient、RadialGradient、SweepGradient这三个渐变效果Shader,大家也可以参考一下博文 《图文详解Andorid中用Shape定义GradientDrawable》 ,该文详细介绍了如何用XML中的 <shape> 节点定义各种具有渐变效果的GradientDrawable,这两篇博文可互为映照。 (责任编辑:好模板) |