恩恩,整了一天多,再次整出一個loading框,看來我對loading框是情有獨鍾,好了,不多bb,先上圖:
恩,就是這么個東東,較之前兩個,有了點技術含量,但是其實也不是很難,之所以做了一天多,原因是又特么踩了一個坑,坑了我一個下午的時間,傷不起,至於是什么坑,下面再說;
好了,完成這個之前必要的知識儲備,二階貝塞爾曲線,也去網上看了一些文章,還有說要三階貝塞爾曲線知識的,其實我覺得沒必要,二階就夠了,下面附上一個鏈接,看完就知道貝塞爾曲線到底是個
什么 東東了:http://blog.csdn.net/cdnight/article/details/48468653
知道了貝塞爾曲線,下面就可以開始說道說道了。
要完成這個loading圖案,我認為難點主要有兩個(不了解如何實現之前),一個是如何實現水波紋效果,一個就是如何將背景的圓和水波紋有效的組合起來(這里有坑,請注意)!
首先是水波紋效果,恩,其實就是兩組正選曲線一直做平移運動(之所以所難點,是因為不揭穿之前,我是一臉懵逼的-_-!!);
然后組合的話就要用到Paint的setXfermode()接口,在里面指定一個PorterDufffermode類的枚舉對象,具體有哪些,這里也附上一個鏈接:http://blog.csdn.net/zidan_2011/article/details/21518351
好了,具體是如何的,上代碼來分析吧!
public class WaveLoading extends View {
private Paint wavePaint;
private Bitmap waveBitmap;
private Canvas waveCanvas; //畫水波紋相關的東東
private Paint cirPaint;
private Bitmap cirBitmap;
private Canvas cirCanvas; //畫背景園相關的東東
private Paint mPaint; //canvas畫布的畫筆
private Bitmap midBitmap;
private Canvas midCanvas;
private Paint midPaint; //中間畫布,用來組合背景圓和水波紋
private int viewWidth; //控件的寬
private int viewHeight; //控件的高
private Path mPath; // 畫水波紋用到的路徑
private int offset; //水波紋達到波動效果的偏移量
private int progress = 0; //進度條
private android.os.Handler mHander = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
invalidate();
}
};
public WaveLoading(Context context) {
this(context, null);
}
public WaveLoading(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveLoading(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initSetting();
}
private void initSetting() {
wavePaint = new Paint();
wavePaint.setColor(Color.parseColor("#0404FF"));
wavePaint.setAntiAlias(true);
wavePaint.setStyle(Paint.Style.FILL);
cirPaint = new Paint();
cirPaint.setColor(Color.parseColor("#88888888"));
cirPaint.setAntiAlias(true);
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
midPaint = new Paint();
midPaint.setStyle(Paint.Style.FILL);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
viewWidth = MeasureSpec.getSize(widthMeasureSpec);
viewHeight = MeasureSpec.getSize(heightMeasureSpec);
offset = -viewWidth;
initBitMap();
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void initBitMap() {
if (waveBitmap == null) {
waveBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
waveCanvas = new Canvas(waveBitmap);
}
if (cirBitmap == null) {
cirBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
cirCanvas = new Canvas(cirBitmap);
}
if (midBitmap == null) {
midBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.ARGB_8888);
midCanvas = new Canvas(midBitmap);
}
}
@Override
protected void onDraw(Canvas canvas) {
cirCanvas.drawCircle(viewWidth / 2, viewHeight / 2, 150, cirPaint);
drawSrc();
midCanvas.drawBitmap(cirBitmap, 0, 0, midPaint);
midPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
midCanvas.drawBitmap(waveBitmap, 0, 0, midPaint);
String percent = progress <= 300 ? progress / 3 + "%" : "100%";
mPaint.setTextSize(50);
mPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText(percent, (viewWidth) / 2, (viewHeight) / 2, mPaint);
canvas.drawBitmap(midBitmap, 0, 0, mPaint);
super.onDraw(canvas);
}
private void drawSrc() {
waveBitmap.eraseColor(Color.parseColor("#00000000"));
mPath = new Path();
wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
for (int i = 0; i < 4; i++) {
if (i % 2 == 0) {
mPath.reset();
mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress - 40,
offset + (1 + i) * viewWidth / 2, viewHeight - progress);
waveCanvas.drawPath(mPath, wavePaint);
} else {
mPath.reset();
mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress + 40,
offset + (1 + i) * viewWidth / 2, viewHeight - progress);
waveCanvas.drawPath(mPath, wavePaint);
}
}
mPath.reset();
waveCanvas.drawRect(0, viewHeight - progress, viewWidth, viewHeight, wavePaint);
}
public void startLoading() {
Timer timer = new Timer();
TimerTask task = new TimerTask() {
@Override
public void run() {
progress++;
offset += 3;
if (offset == 0) offset = -viewWidth;
if (progress == viewHeight + 20) progress = 0;
mHander.obtainMessage(1).sendToTarget();
}
};
timer.schedule(task, 10, 10);
}
}
onMeasure和那些初始化方法就不再分析,直接說重點:
1.水波紋代碼的實現
private void drawSrc() {
waveBitmap.eraseColor(Color.parseColor("#00000000"));
mPath = new Path();
wavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.XOR));
for (int i = 0; i < 4; i++) {
if (i % 2 == 0) {
mPath.reset();
mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress - 40,
offset + (1 + i) * viewWidth / 2, viewHeight - progress);
waveCanvas.drawPath(mPath, wavePaint);
} else {
mPath.reset();
mPath.moveTo(offset + i * viewWidth / 2, viewHeight - progress);
mPath.quadTo(offset + (1 + 2 * i) * viewWidth / 4, viewHeight - progress + 40,
offset + (1 + i) * viewWidth / 2, viewHeight - progress);
waveCanvas.drawPath(mPath, wavePaint);
}
}
mPath.reset();
waveCanvas.drawRect(0, viewHeight - progress, viewWidth, viewHeight, wavePaint);
}
實現后的效果大致是這個樣子的
可以看到,是個很明顯的正弦波,拔高是通過改變progress,波動是通過改變offset實現,具體的代碼在startLoading()方法中,這里使用了PorterDuffXferMode中的Mode.XOR 抑或,根據名稱就知道,顯示不相交的部分,目的在將水波紋的凹陷處去掉;
2.組合水波紋和原型背景
這里用到了PorterDuffXferMode中的Mode.SRC_ATOP,顯示的是下方圖層的全部和上方圖層與下方圖層相交的部分(藍色的水),其實,你們一定會奇怪,你畫就畫吧,直接用canvas在控件上畫不就好了么,干嘛要多此一舉的使用一個midCanvas?
這就得說道我所說的那個坑了,你直接在canvas上面畫的話,設置的PorterDuffXferMode就不起作用,或者說,作用完全是混亂的(我在使用的時候,完全沒有發現什么規律,有知道的朋友可以下方留言我),所以必須使用一塊中間的畫布,來將水波紋和圓組合后再次添加到canvas上,總結一下就是,要在onDraw(Canvas canvas)的canvas上話bitmap的話,最好直接跟它有關的bitmap只有一個;
今天的分享就到這里,記錄生活,共同進步!
更新於 9-13 日, 上面所說的坑, 在看了一篇博文之后得到了解答, 直接在 onDraw(Canvas canvas) 方法中的 canvas 上今日 ‘作畫’ 的話, 畫畫的 DST 就是整個畫布, 而並非只是你畫在畫布上的那一個圖案,所以在使用混合模式 porterDuffXferMode 的時候,得到的圖案就完全不是自己想要的了, 這時候可以像上面所說的, 將畫作在令外的畫布上,再畫回來, 當然,更簡單的方法就是使用離層緩沖, saveLayer();