SurfaceView 实现水波纹动画效果

普通的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线程,但可能会有其它的坑。

代码下载

你可能感兴趣的:(Android,开发)