以前寫as3的時候,遮罩效果一個mask屬性就搞定了,真是方便。
轉到android上以后,發現要實現類似的效果,可以使用Xfermode,android一共提供了三種:
AvoidXfermode; PixelXorXfermode;
PorterDuffXfermode;
前兩種已經不被推薦使用了(據說是因為不支持硬件加速,要生效得強制關閉硬件加速),就不細說了,主要說說第三種,一共提供了十六種效果(as3里也提供了類似,但是更加復雜的方法,所以對我而言還是比較熟悉的),如圖所示:
但是要正確的在canvas上實現這些效果,還真是沒那么容易,我也是研究了半天,終於實現了自己想要的效果,下面用一個例子說明下我的操作流程。
想要實現的效果是這樣的:
簡單分析一下,繪制一個圓形和一個矩形,計算好相應的坐標位置,然后使用SRC_IN進行混合就可以了,類似這樣:
下面說一下我的操作流程:
1. 繪制border
2. 保存為單獨層(canvas.saveLayer),特別注意這一步必須要有,否則無論如何出不來正常效果,起碼我試了很久沒有成功
3. 繪制填充的圓形,同時也是遮罩
4. 設置筆觸的Xfermode為new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
5. 使用該筆觸繪制矩形,進行混合
6. 恢復到canvas上
上代碼,大家可以對照看一下各個步驟的具體代碼
@Override protected void onDraw(Canvas canvas) { float strokeWidth = getResources().getDimension(R.dimen.stroke_width); int borderColor = getResources().getColor(R.color.carnation); int fillColor = getResources().getColor(R.color.carnation_lighter); int percentColor = getResources().getColor(R.color.carnation_light); int width = getWidth(); int height = getHeight(); //border Paint stroke = new Paint(Paint.ANTI_ALIAS_FLAG); stroke.setStrokeWidth(strokeWidth); stroke.setStyle(Paint.Style.STROKE); stroke.setColor(borderColor); canvas.drawOval(new RectF(strokeWidth/2,strokeWidth/2,width-strokeWidth/2,height-strokeWidth/2),stroke); //save as new layer int save = canvas.saveLayer(0,0,width,height,null,Canvas.ALL_SAVE_FLAG); //fill background Paint fill = new Paint(Paint.ANTI_ALIAS_FLAG); fill.setStyle(Paint.Style.FILL); fill.setColor(fillColor); canvas.drawOval(new RectF(strokeWidth - 1, strokeWidth - 1, width - strokeWidth + 1, height - strokeWidth + 1), fill); //mix rect fill.setColor(percentColor); fill.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawRect(0,(1-mPercent)*height,width,height,fill); //restore to canvas canvas.restoreToCount(save); super.onDraw(canvas); }
之前也查了不少文章,貌似沒有看到多少着重說saveLayer的,還是我對照官方apidemos源碼試出來的,希望對遇到同樣疑問的朋友有所幫助!
2015/7/9 更新:
1. 要實現混合的兩個圖形,必須位於同一個layer上,經測試位於不同layer上是無法混合的,即使最后都繪制到了canvas上。
2. 不同的繪制順序,可能有不同的效果,注意一下邏輯即可。
更新一段復雜點的例子
使用了兩種混合方式SRC_IN和CLEAR,主要代碼如下:
1 @Override 2 protected void onDraw(Canvas canvas) { 3 int width = getWidth(); 4 int height = getHeight(); 5 6 float strokeWidth = DimenUtils.dp2px(4); 7 float pointRadius = DimenUtils.dp2px(4); 8 float gap = DimenUtils.dp2px(4); 9 float monthRadius = height * 0.2f; 10 float textSize = DimenUtils.dp2px(14); 11 12 int color = getResources().getColor(R.color.carnation); 13 int lightColor = getResources().getColor(R.color.carnation_light); 14 int lighterColor = getResources().getColor(R.color.carnation_lighter); 15 16 float degree = 360*mRate; 17 18 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 19 20 //new layer 21 int save = canvas.saveLayer(0, 0, width, height, null, Canvas.ALL_SAVE_FLAG); 22 23 //draw percent 24 canvas.save(); 25 float fillDistance = pointRadius+gap+strokeWidth/2; 26 canvas.translate(fillDistance,fillDistance); 27 RectF fillRect = new RectF(0,0,width-2*fillDistance,height-2*fillDistance); 28 paint.setColor(lightColor); 29 paint.setStyle(Paint.Style.FILL); 30 canvas.drawOval(fillRect,paint); 31 //mix rect 32 paint.setColor(color); 33 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); 34 canvas.drawRect(0, (1 - mPercent) * (height-2*fillDistance), width-2*fillDistance, height-2*fillDistance, paint); 35 canvas.restore(); 36 37 //border 38 paint.setXfermode(null); 39 paint.setStrokeWidth(strokeWidth); 40 paint.setStyle(Paint.Style.STROKE); 41 paint.setColor(lighterColor); 42 RectF borderRect = new RectF(pointRadius,pointRadius,width-pointRadius,height-pointRadius); 43 canvas.drawOval(borderRect, paint); 44 paint.setColor(color); 45 canvas.drawArc(borderRect,270,degree,false,paint); 46 //draw point 47 canvas.save(); 48 paint.setStyle(Paint.Style.FILL); 49 canvas.translate(width/2,height/2); 50 canvas.rotate(degree); 51 canvas.drawCircle(0,pointRadius-height/2,pointRadius,paint); 52 canvas.restore(); 53 54 //draw month 55 canvas.save(); 56 canvas.translate(width*0.7f,height*0.8f); 57 paint.setColor(Color.BLACK); 58 paint.setStyle(Paint.Style.STROKE); 59 paint.setStrokeWidth(gap); 60 paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); 61 canvas.drawCircle(0,0,monthRadius+gap/2,paint); 62 paint.setXfermode(null); 63 paint.setStyle(Paint.Style.FILL); 64 paint.setColor(getResources().getColor(R.color.orange)); 65 canvas.drawCircle(0,0,monthRadius,paint); 66 canvas.restoreToCount(save); 67 }