1、BitmapShader
可以通过为paint指定一个渐变(BitmapShader,Paint.setShader(shader)),以图片填充BitmapShader,就可以得到如下圆形图片。
1、渐变模式
BitmapShader,位图渐变,是将图片作为背景,绘制到指定的位图中,如果图片比为图小,则以以下模式进行填充:
- TileMode.CLAMP :不平铺,即AABB型
- TileMode.REPEAT :平铺,即ABABA型
- TileMode.MIRROR:镜像平铺,即ABBA型
效果如下
2、Matrix
可以使用 shader.setLocalMatrix(localM),使Matrix 和渐变结合,实现 位移、旋转、缩放、拉斜的渐变效果,如下可以缩放被填充的图片,移动其中心点到位图的中心点。
主要代码如下
BitmapShader shader=new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//计算缩放比例,缩放被填充的图片,移动其中心点到位图的中心点
Matrix localM=new Matrix();
int bitmapWidth=bitmap.getWidth();
int bitmapHeight=bitmap.getHeight();
float scaleX=width/(bitmapWidth*1.0f);
float scaleY=height/(bitmapHeight*1.0f);
float scale=Math.max(scaleX,scaleY);
localM.setScale(scale,scale);
if(scaleX<scaleY){
localM.postTranslate((bitmapWidth*scaleY-bitmapWidth*scaleX)/2,0);
}else {
localM.postTranslate(0,(bitmapHeight*scaleX-bitmapHeight*scaleY)/2);
}
//缩放、平移渐变
shader.setLocalMatrix(localM);
//为paint指定一个渐变
paint.setShader(shader);
canvas.drawCircle(centerX,centerY,radius,paint);
当然除了圆形,也可以绘制其他任意形状
path.moveTo(0,height/3);
path.lineTo(width,height/3);
path.lineTo(width/4,height);
path.lineTo(width/2,0);
path.lineTo(3*width/4,height);
path.close();
canvas.drawPath(path,paint);
2、xfermode
可以先在画布上画图片,在将xfermode设为DST_IN,在画圆,如下:
代码如下
xfermode=new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
...
int width=getMeasuredWidth();
int height=getMeasuredHeight();
int radius=Math.min(width/2,height/2);
int centerX=width/2;
int centerY=height/2;
desbitmap=Bitmap.createScaledBitmap(bitmap,width,height,true);
circleBitmap=Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888);
Canvas canvas=new Canvas(circleBitmap);
canvas.drawCircle(centerX,centerY,radius,paint);
int layer=canvas.saveLayer(0,0,getMeasuredWidth(),getMeasuredHeight(),null,Canvas.ALL_SAVE_FLAG);
paint.setXfermode(xfermode);
canvas.drawBitmap(desbitmap,0,0,null);
canvas.drawBitmap(circleBitmap,0,0,paint);
paint.setXfermode(null);
canvas.restoreToCount(layer);
当然,也可以是其他形状
1、 关于xfermode
PorterDuffXfermode,可以将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值。当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermode xfermode)方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,如果不想再使用Xfermode,那么可以执行Paint.setXfermode(null)。
一般我们在调用canvas.drawXXX()方法时都会传入一个画笔Paint对象,Android在绘图时会先检查该画笔Paint对象有没有设置Xfermode,如果没有设置Xfermode,那么直接将绘制的图形覆盖Canvas对应位置原有的像素;如果设置了Xfermode,那么会按照Xfermode具体的规则来更新Canvas中对应位置的像素颜色。就本例来说,在执行canvas.drawCirlce()方法时,画笔Paint没有设置Xfermode对象,所以绘制的黄色圆形直接覆盖了Canvas上的像素。当我们调用canvas.drawRect()绘制矩形时,画笔Paint已经设置Xfermode的值为PorterDuff.Mode.CLEAR,此时Android首先是在内存中绘制了这么一个矩形,所绘制的图形中的像素称作源像素(source,简称src),所绘制的矩形在Canvas中对应位置的矩形内的像素称作目标像素(destination,简称dst)。源像素的ARGB四个分量会和Canvas上同一位置处的目标像素的ARGB四个分量按照Xfermode定义的规则进行计算,形成最终的ARGB值,然后用该最终的ARGB值更新目标像素的ARGB值。
本例中的Xfermode是PorterDuff.Mode.CLEAR,该规则比较简单粗暴,直接要求目标像素的ARGB四个分量全置为0,即(0,0,0,0),即透明色,所以我们通过canvas.drawRect()在Canvas上绘制了一个透明的矩形,由于Activity本身屏幕的背景时白色的,所以此处就显示了一个白色的矩形。
PorterDuffXfermode支持以下十几种像素颜色的混合模式,分别为:CLEAR、SRC、DST、SRC_OVER、DST_OVER、SRC_IN、DST_IN、SRC_OUT、DST_OUT、SRC_ATOP、DST_ATOP、XOR、DARKEN、LIGHTEN、MULTIPLY、SCREEN。
我们知道一个像素的颜色由四个分量组成,即ARGB,第一个分量A表示的是Alpha值,后面三个分量RGB表示了颜色。我们用S代表源像素,源像素的颜色值可表示为[Sa, Sc],Sa中的a是alpha的缩写,Sa表示源像素的Alpha值,Sc中的c是颜色color的缩写,Sc表示源像素的RGB。我们用D代表目标像素,目标像素的颜色值可表示为[Da, Dc],Da表示目标像素的Alpha值,Dc表示目标像素的RGB。
源像素与目标像素在不同混合模式下计算颜色的规则如下所示:
SRC:[Sa, Sc],显示上层位图
DST:[Da, Dc],只显示下层位图
SRC_OVER:[Sa + (1 - Sa)Da, Rc = Sc + (1 - Sa)Dc],上下层都显示,运算后上层显示在上面
DST_OVER:[Sa + (1 - Sa)Da, Rc = Dc + (1 - Da)Sc],上下层都显示,运算后下层显示在上面
SRC_IN:[Sa * Da, Sc ** Da],取两层交集部分,内容取决于上层
DST_IN:[Sa * Da, Sa ** Dc],取两层交集部分,内容取决于下层
SRC_OUT:[Sa * (1 - Da), Sc (1 - Da)],取上层非交集部分
DST_OUT:[Da * (1 - Sa), Dc (1 - Sa)],取下层非交集部分
SRC_ATOP:[Da, Sc * Da + (1 - Sa) Dc]
DST_ATOP:[Sa, Sa * Dc + Sc (1 - Da)]
XOR:[Sa + Da - 2 Sa * Da, Sc (1 - Da) + (1 - Sa) Dc]
。。。
注:xfermode,计算的是重叠部分的最终像素
1、des(图片)、src(空心圆)和最终位图大小相等,src是空心圆
2、des(黄圆)、src(蓝,矩形)和最终位图大小相等
参考:Android中Canvas绘图之PorterDuffXfermode使用及工作原理详解
2、关于 Canvas c=new Canvas(mybitmap):
...
Bitmap mybitmap=BitmapFactory.decodeResource(context.getResources(),R.drawable.test3);
boolean isMutable=mybitmap.isMutable();
Canvas c=new Canvas(mybitmap);
...
如果这样设置,会报错:
Immutable bitmap passed to Canvas constructor
就是说如果bitmap不可改变的情况下,canvas是不允许进行绘制的, 当你用BitmapFactory.decodeResource,返回的bitmap是默认状态下的mIsMutable=false。而使用Bitmap.createBitmap()返回的是 true。
...
Bitmap b=BitmapFactory.decodeResource(context.getResources(),R.drawable.test3,options);
mybitmap=Bitmap.createScaledBitmap(b,b.getWidth(),b.getHeight(),true);
boolean isMutable=mybitmap.isMutable();
Canvas c=new Canvas(mybitmap);
...
这种情况下mybitmap依然是b,因为大小一样,就返回了相同的bitmap。
/**
* Creates a new bitmap, scaled from an existing bitmap, when possible. If the
* specified width and height are the same as the current width and height of
* the source bitmap, the source bitmap is returned and no new bitmap is
* created.
*/
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight,
boolean filter) {
...
Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
...
return b;
}
参考: 关于new Canvas(Bitmap)中Bitmap的isMutable的要求、PorterDuffXferMode不正确的真正原因PorterDuffXferMode深入试验) 、在android中画圆形图片的几种办法、 自定义控件三部曲之绘图篇(十)——Paint之setXfermode(一)