一、概述
看見獵豹清理大師的內存開口圓環比例進度 挺有意思的,於是就是想自己實現下這樣的效果,於是反編譯了獵豹清理
大師的app看了下,原來是有兩張圖,於是腦子里就過了下思路,利用上下兩張圖,旋轉上面張圖以及使用
PorterDuffXfermode 來設置合適的渲染模式,就可以達到效果。下面看看咱們的效果吧
二、效果圖
三、Xfermode渲染模式簡介:
xfermode影響在Canvas已經有的圖像上繪制新的顏色的方式
* 正常的情況下,在圖像上繪制新的形狀,如果新的Paint不是透明的,那么會遮擋下面的顏色.
* 如果新的Paint是透明的,那么會被染成下面的顏色
下面的Xfermode子類可以改變這種行為:
AvoidXfermode 指定了一個顏色和容差,強制Paint避免在它上面繪圖(或者只在它上面繪圖)。
PixelXorXfermode 當覆蓋已有的顏色時,應用一個簡單的像素XOR操作。
PorterDuffXfermode 這是一個非常強大的轉換模式,使用它,可以使用圖像合成的16條Porter-Duff規則的任意一條來控制Paint如何與已有的Canvas圖像進行交互。
這里不得不提到那個經典的圖:
上面的16種模式的說明如下:
從上面我們可以看到PorterDuff.Mode為枚舉類,一共有16個枚舉值:
1.PorterDuff.Mode.CLEAR
所繪制不會提交到畫布上。
2.PorterDuff.Mode.SRC
顯示上層繪制圖片
3.PorterDuff.Mode.DST
顯示下層繪制圖片
4.PorterDuff.Mode.SRC_OVER
正常繪制顯示,上下層繪制疊蓋。
5.PorterDuff.Mode.DST_OVER
上下層都顯示。下層居上顯示。
6.PorterDuff.Mode.SRC_IN
取兩層繪制交集。顯示上層。
7.PorterDuff.Mode.DST_IN
取兩層繪制交集。顯示下層。
8.PorterDuff.Mode.SRC_OUT
取上層繪制非交集部分。
9.PorterDuff.Mode.DST_OUT
取下層繪制非交集部分。
10.PorterDuff.Mode.SRC_ATOP
取下層非交集部分與上層交集部分
11.PorterDuff.Mode.DST_ATOP
取上層非交集部分與下層交集部分
12.PorterDuff.Mode.XOR
異或:去除兩圖層交集部分
13.PorterDuff.Mode.DARKEN
取兩圖層全部區域,交集部分顏色加深
14.PorterDuff.Mode.LIGHTEN
取兩圖層全部,點亮交集部分顏色
15.PorterDuff.Mode.MULTIPLY
取兩圖層交集部分疊加后顏色
16.PorterDuff.Mode.SCREEN
取兩圖層全部區域,交集部分變為透明色
四、自定義開口圓環View的實現
1、初始化繪制所需的畫筆,字體顏色、大小等變量
public XCArcProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub degrees = 0; paint = new Paint(); //從attrs.xml中獲取自定義屬性和默認值 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.XCRoundProgressBar); textColor =typedArray.getColor(R.styleable.XCRoundProgressBar_textColor, Color.RED); textSize = typedArray.getDimension(R.styleable.XCRoundProgressBar_textSize, 15); max = typedArray.getInteger(R.styleable.XCRoundProgressBar_max, 100); isDisplayText =typedArray.getBoolean(R.styleable.XCRoundProgressBar_textIsDisplayable, true); typedArray.recycle(); }
2、在onDraw()中繪制出來
在onDraw()方法中利用PorterDuffXfermode渲染模式繪制兩張開口圓環Bitmap,並計算前景圖的旋轉角度,從而達到效果圖效果。
首先先繪制底部背景圖,然后繪制進度前景圖,最后利用PorterDuffXfermode的渲染模式和旋轉角度比例來進行前景圖和背景圖的遮罩處理。
@Override protected void onDraw(Canvas canvas) { // TODO Auto-generated method stub super.onDraw(canvas); int width = getWidth(); int height = getHeight(); int centerX = getWidth() / 2;// 獲取中心點X坐標 int centerY = getHeight() / 2;// 獲取中心點Y坐標 Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); Canvas can = new Canvas(bitmap); // 繪制底部背景圖 bmpTemp = Utils.decodeCustomRes(getContext(), R.drawable.arc_bg); float dstWidth = (float) width; float dstHeight = (float) height; int srcWidth = bmpTemp.getWidth(); int srcHeight = bmpTemp.getHeight(); can.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));// 抗鋸齒 Bitmap bmpBg = Bitmap.createScaledBitmap(bmpTemp, width, height, true); can.drawBitmap(bmpBg, 0, 0, null); // 繪制進度前景圖 Matrix matrixProgress = new Matrix(); matrixProgress.postScale(dstWidth / srcWidth, dstHeight / srcWidth); bmpTemp = Utils.decodeCustomRes(getContext(), R.drawable.arc_progress); Bitmap bmpProgress = Bitmap.createBitmap(bmpTemp, 0, 0, srcWidth, srcHeight, matrixProgress, true); degrees = progress * 270 / max - 270; //遮罩處理前景圖和背景圖 can.save(); can.rotate(degrees, centerX, centerY); paint.setAntiAlias(true); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); can.drawBitmap(bmpProgress, 0, 0, paint); can.restore(); if ((-degrees) >= 85) { int posX = 0; int posY = 0; if ((-degrees) >= 270) { posX = 0; posY = 0; } else if ((-degrees) >= 225) { posX = centerX / 2; posY = 0; } else if ((-degrees) >= 180) { posX = centerX; posY = 0; } else if ((-degrees) >= 135) { posX = centerX; posY = 0; } else if ((-degrees) >= 85) { posX = centerX; posY = centerY; } if ((-degrees) >= 225) { can.save(); Bitmap dst = bitmap .createBitmap(bitmap, 0, 0, centerX, centerX); paint.setAntiAlias(true); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); Bitmap src = bmpBg.createBitmap(bmpBg, 0, 0, centerX, centerX); can.drawBitmap(src, 0, 0, paint); can.restore(); can.save(); dst = bitmap.createBitmap(bitmap, centerX, 0, centerX, height); paint.setAntiAlias(true); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); src = bmpBg.createBitmap(bmpBg, centerX, 0, centerX, height); can.drawBitmap(src, centerX, 0, paint); can.restore(); } else { can.save(); Bitmap dst = bitmap.createBitmap(bitmap, posX, posY, width - posX, height - posY); paint.setAntiAlias(true); paint.setXfermode(new PorterDuffXfermode(Mode.SRC_ATOP)); Bitmap src = bmpBg.createBitmap(bmpBg, posX, posY, width - posX, height - posY); can.drawBitmap(src, posX, posY, paint); can.restore(); } } //繪制遮罩層位圖 canvas.drawBitmap(bitmap, 0, 0, null); // 畫中間進度百分比字符串 paint.reset(); paint.setStrokeWidth(0); paint.setColor(textColor); paint.setTextSize(textSize); paint.setTypeface(Typeface.DEFAULT_BOLD); int percent = (int) (((float) progress / (float) max) * 100);// 計算百分比 float textWidth = paint.measureText(percent + "%");// 測量字體寬度,需要居中顯示 if (isDisplayText && percent != 0) { canvas.drawText(percent + "%", centerX - textWidth / 2, centerX + textSize / 2 - 25, paint); } //畫底部開口處標題文字 paint.setTextSize(textSize/2); textWidth = paint.measureText(title); canvas.drawText(title, centerX-textWidth/2, height-textSize/2, paint); }
3、設置比例進度的同步接口方法,主要供刷新進度比例用。
/** * 設置進度,此為線程安全控件,由於考慮多線的問題,需要同步 * 刷新界面調用postInvalidate()能在非UI線程刷新 * @author caizhiming */ public synchronized void setProgress(int progress) { if(progress < 0){ throw new IllegalArgumentException("progress must more than 0"); } if(progress > max){ this.progress = progress; } if(progress <= max){ this.progress = progress; postInvalidate(); } }
五、源碼下載