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

代码下载

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在Android上使用SurfaceView进行正常播放,您需要完成以下步骤: 1. 创建一个SurfaceView对象,并将其添加到布局中。 2. 在您的Activity中创建一个MediaPlayer对象,将要播放的音频或视频文件的路径传递给它。 3. 设置MediaPlayer的SurfaceViewSurfaceView对象。 4. 准备MediaPlayer实例以进行播放。 5. 在SurfaceView的回调方法surfaceCreated()中启动MediaPlayer实例。 6. 在SurfaceView的回调方法surfaceDestroyed()中释放MediaPlayer实例。 下面是一个示例代码: ```java public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback { private MediaPlayer mediaPlayer; private SurfaceView surfaceView; private SurfaceHolder surfaceHolder; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); surfaceView = findViewById(R.id.surfaceView); surfaceHolder = surfaceView.getHolder(); surfaceHolder.addCallback(this); mediaPlayer = new MediaPlayer(); mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mediaPlayer.setDataSource("path/to/your/media/file"); mediaPlayer.setDisplay(surfaceHolder); mediaPlayer.prepareAsync(); } @Override public void surfaceCreated(SurfaceHolder holder) { mediaPlayer.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { mediaPlayer.release(); } } ``` 在上面的示例中,我们创建了一个SurfaceView对象,并设置了SurfaceHolder的回调方法。我们还创建了一个MediaPlayer对象,并设置了它要播放的音频或视频文件的路径,以及SurfaceView对象作为其显示器。在Activity的onCreate()方法中,我们调用了MediaPlayer的prepareAsync()方法以准备它进行播放。在SurfaceView的surfaceCreated()方法中,我们启动了MediaPlayer实例。最后,在SurfaceView的surfaceDestroyed()方法中,我们释放了MediaPlayer实例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值