
7.4.1 Shader 概述
Shader 在三维软件中称之为着色器,是用来给空白图形上色用的。在 PhotoShop 中有一个印章工具,能够指定印章的样式来填充图形。印章的样式可以是图像、颜色、渐变色等。这里的 Shader 实现的效果与印章类似。我们也是通过给 Shader 指定对应的图像、渐变色等来填充图形的。Paint 中有一个函数专门用于设置 Shader,其声明如下:
Shader 类只是一个基类,其中只有两个函数 setLocalMatrix(Matrix localM) 和 getLocalMatrix(Matrix localM),用来设置坐标变换矩阵的。
Shader 类其实是一个空类,它的功能主要是靠它的派生类来实现的。继承关系如下图所示。

7.4.2 BitmapShader 的基本用法
它的构造函数如下:
这就相当于 PhotoShop 中的印章工具,bitmap 用来指定图案,tileX 用来指定当 X 轴超出单个图片大小时时所使用的重复策略,同样 tileY 用于指定当 Y 轴超出单个图片大小时时所使用的重复策略。
其中TileMode的取值有:
- TileMode.CLAMP:用边缘色彩填充多余空间。
- TileMode.REPEAT:重复原图像来填充多余空间。
- TileMode.MIRROR:重复使用镜像模式的图像来填充多余空间。
1. 示例
这里使用的印章图像如下图所示 (dog.png)。

中间是一幅小狗头像,四周被四种不同的颜色给包围。设置 Shader 的完整代码如下:
效果图如下所示:

给自定义的控件添加上宽高限制:
效果图如下:

从效果图中可以看出:
- 在 X 轴和 Y 轴都使用 REPEAT 模式下,在超出单个图像的区域后,就会重复绘制这个图像。
- 绘制是从控件的左上角开始的,而不是从屏幕原点开始的。这点很好理解,因为我们只会在自定义控件上绘图,不会在全屏幕上绘图。
2. TileMode 模式解析
上面初步看到了 REPEAT 模式的用法,现在我们分别来看在各个模式下的不同表现。
1)TileMode.REPEAT 模式:重复原图像来填充多余空间
在更改模式时,只需要更新 setShader 里的代码:
在这里,X 轴、Y 轴全部设置成 REPEAT 模式,所以当控件的显示范围超出了单个图的显示范围时,在 X 轴上将使用 REPEAT 模式;同样,在 Y 轴上也将使用 REPEAT 模式。
2)TileMode.MIRROR 模式:重复使用镜像模式的图像来填充多余空间
同样,将 X 轴、Y 轴全部改为 MIRROR 模式:
效果如下图所示。

在 X 轴上每两张图片的显示都像镜子一样翻转一下。同样,在 Y 轴上每两张图片的显示也都像镜子一样翻转一下。所以这就是镜相效果的作用,镜像效果其实就是在显示下一图片的时候,就相当于两张图片中间放了一个镜子一样。
3)TileMode.CLAMP:用边缘色彩填充多余空间

CLAMP 模式的意思就是当控件区域超过当前单个图片的大小时,空白位置的颜色填充就用图片的边缘颜色来填充。
4)TileMode.CLAMP 与填充顺序
当 X 轴、Y 轴全部都是 CLAMP 模式时,X 轴的空白区域会用图像的右侧边缘颜色来填充;Y 轴的空白区域会用图像的底部的边缘颜色来填充,那效果应该是这样的:

明显右下角的空白位置根本与图像是不沾边的,那它要用什么颜色来填充呢?是填充上方的蓝色还是填充左侧的绿色呢?
从最终的效果图来看,这部分填充的颜色是绿色的,可为什么呢?其实这是跟填充顺序有关的,并且是先填充竖向再填充横向。如果是先填充横向再填充竖向,那么右下角颜色应该是蓝色。
4)使用混合填充模式
比如在 X 轴填充空白区域时使用 MIRROR 样式、在填充 Y 轴空白区域时使用REPEAT样式:

从效果图中可以看出来,首先使用 REPEAT 模式填充 Y 轴,然后使用 MIRROR 模式填充 X 轴。
总之:无论哪两种模式混合或者相同模式,都是先填充 Y 轴,然后填充 X 轴。
3. 绘图位置与图像显示
在上面的例子中,我们利用 drawRect 把整个控件大小都给覆盖了,那假如我们只画一个小矩形而不完全覆盖整个控件,那我们 setShader() 函数中所设置的图片是从哪里开始画的呢?
即在绘图时,并不是完全覆盖控件大小的,而是取控件中间位置的 1/3 区域显示的。效果如下图所示。

7.4.3 示例一:望远镜效果

这里要实现的效果是:根据手指所在的位置,把对应的图像绘制出来。这样看起来就是望远镜效果了。
我们主要来看下 OnDraw() 函数:
在 onDraw() 函数中,第一部分,就是新建一个空白的 bitmap,这个 bitmap 的大小与控件一样,然后把我们的背景图进行拉伸,画到这个空白的 bitmap 上。由于这里的 canvasBg 是用 mBitmapBg 创建的,所以所画的任何图像都会直接显示在 mBitmapBg 上,而我们创建的 mBitmapBg 是与控件一样大的,所以当把 mBitmapBg 做为 Shader 来设置给 mPaint 时,mBitmapBg 会正好覆盖整个控件,而不会有多余的空白像素。
这里需要注意的就是我们在将原图像画到 mBitmapBg 时,进行了拉伸压缩,把它拉伸到和当前控件一样大小。然后利用 OnMotionEvent 来捕捉用户的手指位置,当用户手指下按时,在手指位置画一个半径为 mRadius 的圆形,把对应的位置的图像显示出来就可以了。
7.4.4 示例二:生成不规则头像

res/values/attrs.xml
自定义控件:
在 XML 中使用:
