Android绘图篇(四)——Paint详解
上一篇总结了下Paint的绝大部分方法。还有两个非常重量级的方法,setShader和setXfermode,废话不多说,先来总结下setShader的使用,setXfermode比较复杂,下一篇再总结。
setShader,顾名思义,设置着色器,我们知道在Canvas中,我们调用drawXXX可以绘制出各种各样的图形,如圆形、矩形、扇形等等,而Shader是给Paint设置的属性,决定paint绘制图形的时候如何给图形上色,比如绘制一个矩形,我想要矩形中铺满一张图片,那这些平铺的图片就相当于是给这个矩形上色了。好了,概念先说到这,Shader是一个基类,setShader中设置的都是Shader的子类,Shader共有以下几个子类:
1、public BitmapShader(Bitmap bitmap,TileMode tileX, TileMode tileY)
2、public LinearGradient(float x0, float y0, float x1, float y1,int color0, int color1,TileMode tile)
3、public RadialGradient(float centerX, float centerY, float radius,int centerColor, int edgeColor,TileMode tileMode)
4、public SweepGradient(float cx, float cy, int color0, int color1)
5、 public ComposeShader(Shader shaderA,Shader shaderB, Xfermode mode)
除了BitmapShader,其它的几个子类都有重载方法,稍后会介绍。
图片着色器,用图片为图形上色,比如我们上面的例子,绘制一个矩形,里面用一张图片铺满,好吧,先准备一张微信图标:
嗯,还蛮好看的不是。我们先来看下BitmapShader的构造方法,第一个参数bitmap好理解,第二个参数和第三个参数是啥意思呢?这两个参数都是Shader.TileMode类型,有以下几个取值:
1、CLAMP 当所画图形的尺寸大于Bitmap的尺寸的时候,会用Bitmap四边的颜色填充剩余空间。
2、REPEAT 当我们绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域。
3、MIRROR 当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
好吧,看下第一种效果呗:CLAMP
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取Bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wechat);
//Bitmap着色器
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//设置着色器
mPaint.setShader(bitmapShader);
canvas.drawRect(0, 0, 800, 600, mPaint);
}
然后我们可以看下效果:
可以看到,绘制的矩形的宽高为800*600,远远大于该Bitmap,于是矩形中剩余的部分都被图片四周的颜色所填充,至于为什么看起来剩余填充的那一部分要比宽和高小一点,是因为这是一张圆角图,圆角有一小部分是透明的,在Android Studio里面可以很明显的看出来:
所以:
红色的那部分才会延伸出颜色,透明的那部分也会延伸出颜色,只不过是透明的,所以导致延伸出来的部分的宽度要小于矩形图标的真实宽度。这是绘制矩形的宽高大于Bitmap的宽高,如果绘制图形的宽高小于Bitmap的宽高呢?
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取Bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wechat);
//Bitmap着色器
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//设置着色器
mPaint.setShader(bitmapShader);
canvas.drawCircle(bitmap.getWidth() / 2, bitmap.getHeight() / 2, 150, mPaint);
}
这里以图片的中心点为原点,150为半径,画出一个圆,则:
看起来是对原Bitmap进行了一次裁剪,把中间的部分裁了出来,利用这个原理,我们可以去制造圆形头像。
REPEAT
REPEAT表示所绘制的图形大于Bitmap时,会用Bitmap重复平铺整个绘制的区域。举个栗子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取Bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wechat);
//Bitmap着色器
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
//设置着色器
mPaint.setShader(bitmapShader);
canvas.drawRect(0, 0, 900, 700, mPaint);
}
然后:
MIRROR
与REPEAT类似,当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取Bitmap
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.wechat);
//Bitmap着色器
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
//设置着色器
mPaint.setShader(bitmapShader);
canvas.drawRect(0, 0, 900, 700, mPaint);
}
效果如下:
可以发现,这种模式下任然会铺满整个图形,不同的是上下左右互为镜像。以上关于BitmapShader,x和y方向的Shader.TileMode我们设置的都一样,当然可以设置不同的Shader.TileMode,然后去看下什么效果,这里就不展示了。
线性渐变效果,可以让图形从一点到另一点的整个区域的颜色呈线性渐变。先来看下第一个构造函数:
public LinearGradient(float x0, float y0, float x1, float y1,int color0, int color1,TileMode tile)
x0,y0是起始位置,x1,y1是结束位置,color0是起始颜色,color1是结束颜色,从(x0,y0)到(x1,y1)的整个区域内,颜色由color0到color1线性渐变,说了这么多不如举个例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//LinearGradient
LinearGradient linearGradient = new LinearGradient(100, 100, 500, 500, Color.BLUE, Color.GREEN, Shader.TileMode.CLAMP);
//设置着色器
mPaint.setShader(linearGradient);
canvas.drawRect(100, 100, 500, 500, mPaint);
}
emmmm~:
从矩形的左上角到右下角,颜色从蓝到绿色线性渐变。不过这里我们颜色的起点和终点之间的矩形恰好和我们绘制的矩形一样大,所以Shader.TileMode.CLAMP就没起到作用,如果我们把绘制的矩形增大:
canvas.drawRect(100, 100, 800, 800, mPaint);
可以发现:
剩余的都被绿色填充咯,然后我们再把矩形增大一点,更改下模式,改为Shader.TileMode.REPEAT
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//LinearGradient
LinearGradient linearGradient = new LinearGradient(0, 0, 500, 500, Color.BLUE, Color.GREEN, Shader.TileMode.REPEAT);
//设置着色器
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 1200, 1920, mPaint);
}
然后就会发现蓝绿蓝绿蓝绿蓝绿这样不停的交替:
修改为 Shader.TileMode.MIRROR,则:
是不是还有个重载的方法:
LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)
理解了第一个构造方法,这个构造方法也挺好理解,前面四个参数意义不变,colors数组表示可以传入多个颜色值,比如传入三种颜色,红、绿、蓝,positions数组的意义稍微有点难以理解,我们先看个例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//LinearGradient
int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
float[] positions = new float[]{0.3f, 0.6f, 0.8f};
LinearGradient linearGradient = new LinearGradient(0, 0, 500, 500, colors, positions, Shader.TileMode.CLAMP);
//设置着色器
mPaint.setShader(linearGradient);
canvas.drawRect(0, 0, 500, 500, mPaint);
}
我们先来看下效果,再来说规律:
首先我们要明确两点:
1、positions数组元素的个数一定colors数组的个数一样,多一个或者少一个,程序都会崩溃。
2、positions中元素的取值范围是0~1,不可小于0或大于1。
这就是这个方法参数的规约,没什么好说的。关键在于positions中元素代表的意义:
就如上面的例子,positions是{0.3f, 0.6f, 0.8f},那么0.3f至0.6f是红色到绿色渐变;0.6f~0,.8f是绿色到蓝色渐变,0.3f、0.6f、0.8f分别是渐变线上的位置,0.3f就代表渐变线上30%的位置,0.6f代表渐变线上60%的位置,0.8f一样类推。那么[0f,0.3f]这个范围是啥颜色呢?当然是红色咯,同理,[0.8f,1f]这个范围是蓝色。 关于这个,跑个例子就很容易理解。
当然,positions数组可以传null,就是三种颜色依次渐变。和传入[0f,0.5f,1f]的效果是一样的哈,感兴趣的可以自己试试。然后我们缩小LinearGradient的范围,并把模式改为Shader.TileMode.MIRROR:
LinearGradient linearGradient = new LinearGradient(0, 0, 100, 100, colors, positions, Shader.TileMode.CLAMP);
其它代码不动,可以看到如下效果:
可以发现,每两个相同颜色间都是对称的,虽然长短不同,但颜色所占的范围一定是对称的,比如两块蓝色区域之间,折叠起来就可以发现,绿色和绿色相对,红色区域一分为二,也是对称的。好了,再试下 Shader.TileMode.REPEAT:
欧了,LinearGradient到此为止。
public RadialGradient(float centerX, float centerY, float radius,int centerColor int edgeColor,TileMode tileMode)
一个圆形范围的着色器
参数:
1、centerX:渐变中心点X坐标
2、centerY:渐变中心点Y坐标
3、radius:渐变半径
4、centerColor:渐变起始颜色
5、渐变结束颜色
6、模式。
好吧,来玩一个:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//RadialGradient
RadialGradient radialGradient = new RadialGradient(canvas.getWidth()/2,canvas.getHeight()/2,200,Color.RED,Color.GREEN,Shader.TileMode.CLAMP);
//设置着色器
mPaint.setShader(radialGradient);
canvas.drawCircle(canvas.getWidth()/2,canvas.getHeight()/2,200, mPaint);
}
在(500,500)的位置画一个半径为200的圆,着色器的范围和所绘制的圆位置一样,大小一样,然后
从中心到边缘,颜色由红色变成绿色。当然这个着色器的范围和所绘制的圆形的范围一样大,所以Shader.TileMode.CLAMP没有效果,如果我们调小着色器的范围:
RadialGradient radialGradient = new RadialGradient(500,500,60,Color.RED,Color.GREEN,Shader.TileMode.REPEAT);
把200改成60:
这时Shader.TileMode.REPEAT就发挥作用了,除了中间一小部分是红色,其余都被绿色填满。然后我们再修改下模式为Shader.TileMode.REPEAT:
RadialGradient radialGradient = new RadialGradient(500,500,60,Color.RED,Color.GREEN,Shader.TileMode.REPEAT);
然后:
emmm~,有点像棒棒糖。再来,改为Shader.TileMode.MIRROR:
红色到绿色渐变,接着绿色到红色渐变,然后红色到绿色渐变以此往复,刚好符合我们Shader.TileMode.MIRROR的特征。
好了,它也有一个重载的方法:
public RadialGradient(float centerX, float centerY, float radius,int colors[], float stops[],tileMode)
和第一个构造方法差不多,不同的是第四个和第五个参数,colors数组和我们之前讲
LinearGradient中的clolors数组一样,stops数组也相当于我们刚说的LinearGradient中的positions数组,由此可见,懂了LinearGradient中参数的意义,这里也是一样的呀。
走你~
int [] colors = new int[]{Color.RED,Color.GREEN,Color.BLUE};
float [] stops = new float[]{0.3f,0.6f,0.8f};
RadialGradient radialGradient = new RadialGradient(500, 500, 200,colors,stops, Shader.TileMode.CLAMP);
看下呗:
0.3-0.6是红色渐变为绿色,0.6-0.8是绿色渐变为蓝色,0-0,3是红色,0.8-1是蓝色,和线性渐变的规律一样,同样两个数组元素的数量要相同。stops数组元素的取值从0到1,且依次增大。
调小下着色器的范围,把200改为60:
RadialGradient radialGradient = new RadialGradient(500, 500, 60,colors,stops, Shader.TileMode.CLAMP);
嗯:
Shader.TileMode.CLAMP模式生效,再改为Shader.TileMode.REPEAT
效果也符合REPEAT的规律。MIRROR模式这里就不展示了,自行脑补。RadialGradient 结束。
也是一个扫描着色器,先看构造方法:
public SweepGradient(float cx, float cy, int color0, int color1)
参数释义:
1、cx:中心点x坐标
2、cx:中心点y坐标
3、扫描开始时的颜色
3、扫描结束时的颜色
来个例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
SweepGradient radialGradient = new SweepGradient(500, 500, Color.RED,Color.GREEN);
//设置着色器
mPaint.setShader(radialGradient);
canvas.drawCircle(500, 500, 200, mPaint);
}
你会发现这样的画面:
红色和绿色各自占了圆的一半,且是以扫描的形式,比如红色,红色越来越淡,直到变成绿色,颇有点雷达的意思。当然我们如果想绘制一个矩形,然后也有类似的效果呢?当然可以,不过你得保证着色器中心点的位置也是矩形中心点的位置,举个例子,假如你想绘制一个宽600,高400的矩形,矩形的中心点在着色器的中心点,那么你需要:
canvas.drawRect(300, 300, 700,700, mPaint);
然后便可以发现效果了:
一样的效果哈。这个构造方法只会有两种颜色,两种颜色各自占所绘制图形扫描总区域的一半。看图也能发现是什么效果
同样也有个重载的方法:
public SweepGradient(float cx, float cy,int colors[], float positions[])
第一个参数和第二个参数自不必说,第三个和第四个参数和LinearGradient中所代表的一样。好了 ,跑个例子:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE};
float[] positions = new float[]{0.3f, 0.6f, 0.8f};
SweepGradient radialGradient = new SweepGradient(500, 500, colors, positions);
//设置着色器
mPaint.setShader(radialGradient);
canvas.drawRect(300, 300, 700, 700, mPaint);
}
嗯
和LinearGradient的参数有异曲同工之妙,唯一不同的是,SweepGradient不需要传递着色模式了,比如 Shader.TileMode.CLAMP,压根不需要的。当然colors数组和positions数组之间的规律参照前两种着色器两数组之间的规律。positons可以传null,结果如下:
和positions数组传入[0f,0.5f,1f]的效果是一样的哈。
最后还有个ComposeShader,这里先不讲,是组合两种着色器效果的,但是涉及到另一个方法setXfermode ,等讲完setXfermode再去总结最后一个着色器,呼~着色器的部分终于讲完了: