前言
最近專案需要接入環信客服 SDK
,我配合這同事完成,其中我負責檔案下載這部分。
因為時間比較緊張,8 天的時間完成 環信客服模組 的接入,就直接用了環信提供的 UI
控制元件,但是一些細節的部分, UI
還是會給出設計圖,按照設計圖完成最終效果。
UI
那邊直接讓我參考 IOS
的實現效果:
最終效果
原始碼請看 DownloadLoadingView
功能分析
面對這樣的需要應該怎麼實現呢?其實實現的方式可能不止我想的這種,我就講述一下我是如何處理的。
首先,可以分成三部分:
- 半透明的背景
- 全透明的環
- 實心全透明的弧
那怎麼實現背景半透明,而圓環和弧又是全透明的。頓時有個想法,要是兩張圖片重疊的部分能被摳出掉,也就是變成全透明,那豈不是非常容易就實現了。
圓環和弧既然是蓋在了背景上,理當直接變成透明。那 Android
有對應處理的 API
嗎?答案是肯定的。 setXfermode() 用於設定影象的過度模式,其中 PorterDuff.Mode.CLEAR 為清除模式則可以實現上述的效果。
具體實現
一系列的初始化
public DownloadLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.DownloadLoadingView);
mRadius = typedArray.getDimension(R.styleable.DownloadLoadingView_radius, RADIUS_DEFAULT);
mStrokeWidth = typedArray.getDimension(R.styleable.DownloadLoadingView_strokeWidth, STROKE_WIDTH_DEFAULT);
mMaxProgress = typedArray.getInteger(R.styleable.DownloadLoadingView_maxProgress, MAX_PROGRESS_DEFAULT);
mRoundRadius = typedArray.getDimension(R.styleable.DownloadLoadingView_roundRadius, ROUND_RADIUS_DEFAULT);
mBackgroundColor = typedArray.getColor(R.styleable.DownloadLoadingView_backgroundColor, getResources().getColor(R.color.bg_default));
Log.i(TAG, "radius:" + mRadius);
typedArray.recycle();
setLayerType(View.LAYER_TYPE_SOFTWARE, null);//關閉硬體加速
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
}
複製程式碼
記得需要關閉硬體加速,不然會沒有效果。
繪製背景
paint.setColor(mBackgroundColor);
paint.setStyle(Paint.Style.FILL);
RectF round = new RectF(0, 0, getWidth(), getHeight());
canvas.drawRoundRect(round, mRoundRadius, mRoundRadius, paint);
複製程式碼
設定背景顏色,樣式為填充,繪製圓角矩形
繪製圓環
paint.setColor(Color.RED);
paint.setStrokeWidth(mStrokeWidth);
// 採用 clear 的方式
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(getWidth() / 2, getHeight() / 2, mRadius, paint);
複製程式碼
圓環的顏色可以隨意設定,畢竟最後會被摳除掉,
設定
PorterDuff.Mode.CLEAR
型別模式繪製圓環
繪製圓
paint.setStyle(Paint.Style.FILL);
float sweepAngle = 360 * mProgress / mMaxProgress;
RectF rectF = new RectF(getWidth() / 2 - mRadius, getHeight() / 2 - mRadius, getWidth() / 2 + mRadius, getHeight() / 2 + mRadius);
canvas.drawArc(rectF, -90, sweepAngle, true, paint);
// 記得設定為 null 不然會沒有效果
paint.setXfermode(null);
複製程式碼
根據當前的進度繪製相對應的弧,並且結束的時候將
Xfermode
模式置為null
。
這樣效果就結束了,賊簡單。完整的程式碼請看 DownloadLoadingView
擴充
文中提到了 PorterDuff.Mode
,裡面儲存了大量的列舉,當我們需要處理影象的時候就會用到,但是對每種型別並沒有特別的瞭解。每次使用的時候都需要查資料,然後確定到底需要使用哪種模式。
public Xfermode setXfermode(Xfermode xfermode) {
long xfermodeNative = 0;
if (xfermode != null)
xfermodeNative = xfermode.native_instance;
native_setXfermode(mNativePaint, xfermodeNative);
mXfermode = xfermode;
return xfermode;。
}
複製程式碼
具體的模式:
public enum Mode {
/** [0, 0] */
CLEAR (0),
/** [Sa, Sc] */
SRC (1),
/** [Da, Dc] */
DST (2),
/** [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] */
SRC_OVER (3),
/** [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] */
DST_OVER (4),
/** [Sa * Da, Sc * Da] */
SRC_IN (5),
/** [Sa * Da, Sa * Dc] */
DST_IN (6),
/** [Sa * (1 - Da), Sc * (1 - Da)] */
SRC_OUT (7),
/** [Da * (1 - Sa), Dc * (1 - Sa)] */
DST_OUT (8),
/** [Da, Sc * Da + (1 - Sa) * Dc] */
SRC_ATOP (9),
/** [Sa, Sa * Dc + Sc * (1 - Da)] */
DST_ATOP (10),
/** [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] */
XOR (11),
/** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] */
DARKEN (12),
/** [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] */
LIGHTEN (13),
/** [Sa * Da, Sc * Dc] */
MULTIPLY (14),
/** [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] */
SCREEN (15),
/** Saturate(S + D) */
ADD (16),
OVERLAY (17);
Mode(int nativeInt) {
this.nativeInt = nativeInt;
}
/**
* @hide
*/
public final int nativeInt;
}
複製程式碼
註釋中已經說明了該模式到的透明度計算和顏色的計算方式,首先我們要了解一下基本的概念:
Sa:全稱為Source alpha,表示源圖的Alpha通道;
Sc:全稱為Source color,表示源圖的顏色;
Da:全稱為Destination alpha,表示目標圖的Alpha通道;
Dc:全稱為Destination color,表示目標圖的顏色.
複製程式碼
來看一下權威的展示圖:
我覺得 各個擊破搞明白 PorterDuff.Mode 這篇文章寫的特別好,不是很懂的小夥伴可以看一下,在這裡也表示一下感謝。