普通的view绘制水波纹动画会比较卡,因为它的绘制也是在主线程上的,会影响到UI线程,于是我使用SurfaceView来实现这个效果,它可以在单独一个线程里进行绘制,这样对UI线程的影响就减少了,先看效果,拍的视频比较模糊。
一、总体思路
1、使用path绘制3条正弦曲线,正弦曲线的周期为屏幕宽度,这里绘制两个周期
2、不断改变path的起点,重新绘制正弦曲线,这样就可以让它动起来了
3、在SurfaceView实现上面的绘制操作
二、代码实现
1、继承SurfaceView,在构造函数实现初始化
private void initView(Context context) {
//SurfaceHolder
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
//去除黑底
setZOrderOnTop(true);
getHolder().setFormat(PixelFormat.TRANSLUCENT);
mWavePaint = new Paint();// 初始绘制波纹的画笔
mWavePaint.setAntiAlias(true);// 去除画笔锯齿
mWavePaint.setStyle(Paint.Style.FILL);// 设置风格为实线
mWavePaint.setColor(WAVE_PAINT_COLOR);// 设置画笔颜色
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
path = new Path();
path2 = new Path();
path3 = new Path();
// 将dp转化为px,用于控制不同分辨率上移动速度基本一致
mXOffsetSpeed_1 = ViewUtils.dp2px(context, TRANSLATE_X_SPEED_1);
mXOffsetSpeed_2 = ViewUtils.dp2px(context, TRANSLATE_X_SPEED_2);
mXOffsetSpeed_3 = ViewUtils.dp2px(context, TRANSLATE_X_SPEED_3);
}
刚开始时候没有绘图的部分有黑底,后来找资料要设置setZOrderOnTop(true);
getHolder().setFormat(PixelFormat.TRANSLUCENT);这样才不会黑底
2、在surfaceChanged方法中初始化水波纹的高度和宽度
private void initParam(int w, int h) {
// 记录下view的宽高
mTotalWidth = w;
mTotalHeight = h;
//水波纹高度
mWaveHeight = mTotalHeight * 0.12f;
//水波纹半个周期,屏幕的1/2宽度
mWaveWidth = mTotalWidth * 0.5f;
//水波纹的1/4周期,屏幕的1/4宽度,用于设置控制点的坐标
mWaveHalfWidth = mTotalWidth * 0.25f;
}
这个方法在surfaceview创建时,至少会执行一次。
3、绘制水波纹,3条动态的正弦曲线
private void draw() {
try {
canvas = mHolder.lockCanvas();
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//清屏
if(mDrawFilter==null)
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
canvas.setDrawFilter(mDrawFilter);// 从canvas层面去除绘制时锯齿
path.reset();
path2.reset();
path3.reset();
//改变path的起点,实现动态效果
path.moveTo(-mTotalWidth + mXOffset_1, mWaveHeight);
path2.moveTo(-mTotalWidth + mXOffset_2, mWaveHeight);
path3.moveTo(-mTotalWidth + mXOffset_3, mWaveHeight);
//使用rQuadTo画完两个周期的正弦曲线
for (int i = 0; i < 2; i++) {
//rQuadTo 参数的坐标点是相对的,这里相对原点
path.rQuadTo(mWaveHalfWidth, -mWaveHeight, mWaveWidth, 0);
path.rQuadTo(mWaveHalfWidth, mWaveHeight, mWaveWidth, 0);
path2.rQuadTo(mWaveHalfWidth, -mWaveHeight, mWaveWidth, 0);
path2.rQuadTo(mWaveHalfWidth, mWaveHeight, mWaveWidth, 0);
path3.rQuadTo(mWaveHalfWidth, -mWaveHeight, mWaveWidth, 0);
path3.rQuadTo(mWaveHalfWidth, mWaveHeight, mWaveWidth, 0);
}
//把路径封闭
path.lineTo(mTotalWidth, mTotalHeight);
path.lineTo(0, mTotalHeight);
path.close();
path2.lineTo(mTotalWidth, mTotalHeight);
path2.lineTo(0, mTotalHeight);
path2.close();
path3.lineTo(mTotalWidth, mTotalHeight);
path3.lineTo(0, mTotalHeight);
path3.close();
canvas.drawPath(path, mWavePaint);
canvas.drawPath(path2, mWavePaint);
canvas.drawPath(path3, mWavePaint);
//移动速度的计算
mXOffset_1 += mXOffsetSpeed_1;
mXOffset_2 += mXOffsetSpeed_2;
mXOffset_3 += mXOffsetSpeed_3;
//超过屏幕宽度,重置为0
if (mXOffset_1 > mTotalWidth) mXOffset_1 = 0;
if (mXOffset_2 > mTotalWidth) mXOffset_2 = 0;
if (mXOffset_3 > mTotalWidth) mXOffset_3 = 0;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (canvas != null)
mHolder.unlockCanvasAndPost(canvas);
}
}
要注意的地方是:
(1)需要清屏,不然绘制的东西就重叠在一起了,canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//清屏
(2)rQuadTo 参数的坐标点是相对的,这里相对原点;path.rQuadTo(mWaveHalfWidth, -mWaveHeight, mWaveWidth, 0);
是用来画上面的那部正弦曲线
path.rQuadTo(mWaveHalfWidth, mWaveHeight, mWaveWidth, 0);
是用来画下面那部分的正弦曲线
(3)path需要重置,起点也需要不断改变,才可以实现动态效果
path.reset();
path.moveTo(-mTotalWidth + mXOffset_1, mWaveHeight);
4、在线程里绘制动画
@Override
public void run() {
while (mIsDrawing) {
draw();
}
}
//在外部调用此方法,启动线程、绘制动画
public void setStart() {
if (!mIsDrawing) {
mIsDrawing = true;
if(thread==null)
thread = new Thread(this);
thread.start();
}
}
//提供给外部停止动画的方法
public void setStop() {
mIsDrawing = false;
thread = null;
}
5、使用此控件
在activity中使用此控件,有些手机第一次会闪一下(黑屏),之后就没事了,有网友建议在之前的其它布局写一个宽、高都为0的surfaceView;我这里就在activity中onResume时候才去显示SurfaceView,在onStop()方法中停止它。
@Override
protected void onResume() {
super.onResume();
handler.post(new Runnable() {
@Override
public void run() {
sfv.setVisibility(View.VISIBLE);
sfv.setStart();
}
});
}
@Override
protected void onStop() {
sfv.setVisibility(View.GONE);
sfv.setStop();
super.onStop();
}
三、总结
使用SurfaceView 需要注意黑底的去除和清屏,不然达不到预期的效果。用SurfaceView 的好处是可以避免阻塞UI线程,但可能会有其它的坑。
代码下载