恩恩,整了一天多,再次整出一个loading框,看来我对loading框是情有独钟,好了,不多bb,先上图:
恩,就是这么个东东,较之前两个,有了点技术含量,但是其实也不是很难,之所以做了一天多,原因是又特么踩了一个坑,坑了我一个下午的时间,伤不起,至于是什么坑,下面再说;
好了,完成这个之前必要的知识储备,二阶贝塞尔曲线,也去网上看了一些文章,还有说要三阶贝塞尔曲线知识的,其实我觉得没必要,二阶就够了,下面附上一个链接,看完就知道贝塞尔曲线到底是个
什么 东东了:
知道了贝塞尔曲线,下面就可以开始说道说道了。
要完成这个loading图案,我认为难点主要有两个(不了解如何实现之前),一个是如何实现水波纹效果,一个就是如何将背景的圆和水波纹有效的组合起来(这里有坑,请注意)!
首先是水波纹效果,恩,其实就是两组正选曲线一直做平移运动(之所以所难点,是因为不揭穿之前,我是一脸懵逼的-_-!!);
然后组合的话就要用到Paint的setXfermode()接口,在里面指定一个PorterDufffermode类的枚举对象,具体有哪些,这里也附上一个链接:
好了,具体是如何的,上代码来分析吧!
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();