贝塞尔曲线

刚做的一个项目当中,加了一个功能,来电话,并且屏幕向下的时候,做一个动画效果。效果如下:

这是静态图片,动起来的时候就是波浪运动效果。

先上贝塞尔的实现(我这个是网上找的)


import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.LinearInterpolator;

public class WaveViewByBezier extends View {

    /**
     * 屏幕高度
     */
    private int mScreenHeight;
    /**
     * 屏幕宽度
     */
    private int mScreenWidth;

    /**
     * 波浪的画笔
     */
    private Paint mWavePaint;
    /**
     * 一个周期波浪的长度
     */
    private int mWaveLength;

    /**
     * 波浪的路径
     */
    Path mWavePath;
    
    Path mWavePathTwo;

    /**
     * 平移偏移量
     */
    private int mOffset;

    /**
     * 一个屏幕内显示几个周期
     */
    private int mWaveCount;

    /**
     * 振幅
     */
    private int mWaveAmplitude;

    /**
     * 波形的颜色
     */
    private int waveColor = 0xFF05FF3A;

    private static final int SIN = 0;
    private static final int COS = 1;
    private static final int DEFAULT = SIN;

    private int waveType = DEFAULT;

    private ValueAnimator valueAnimator;

    public WaveViewByBezier(Context context) {
        this(context, null);

    }

    public WaveViewByBezier(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        mWaveAmplitude = dp2px(15);
        mWaveLength = dp2px(600);
        
        initPaint();
    }

    /**
     * 初始化画笔
     */
    private void initPaint() {
        mWavePath = new Path();

        mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mWavePaint.setColor(waveColor);
        mWavePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mWavePaint.setAntiAlias(true);
        
        mWavePathTwo = new Path();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        mScreenHeight = h;
        mScreenWidth = w;

        /**
         * 加上1.5是为了保证至少有两个波形(屏幕外边一个完整的波形,屏幕里边一个完整的波形)
         */
        //mWaveCount = (int) Math.round(mScreenWidth / mWaveLength + 1.5);
        mWaveCount = (int) Math.round(mScreenHeight / mWaveLength + 1.5);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        switch (waveType) {
        case SIN:
            drawSinPath(canvas);
            drawSinPathTwo(canvas);
            break;
        case COS:
            drawCosPath(canvas);
            break;
        }

    }

    /**
     * sin函数图像的波形
     *
     * @param canvas
     */
    private void drawSinPath(Canvas canvas) {
        //mWavePath.reset();

        mWavePath.moveTo(0, -mWaveLength + mOffset);

        // TODO: 2017/6/19 //相信很多人会疑惑为什么控制点的纵坐标是以下值,是根据公式计算出来的,具体计算方法情况文章内容

        for (int i = 0; i < mWaveCount; i++) {

            // 第一个控制点的坐标为(-mWaveLength * 3 / 4,-mWaveAmplitude)
            mWavePath.quadTo(0, -mWaveLength * 3 / 4 + mOffset + i * mWaveLength, mWaveAmplitude,  -mWaveLength / 2 + mOffset + i * mWaveLength);
            // 第二个控制点的坐标为(-mWaveLength / 4,3 * mWaveAmplitude)
            mWavePath.quadTo(2 * mWaveAmplitude, -mWaveLength / 4 + mOffset + i * mWaveLength, 0,  mOffset + i * mWaveLength);
        }

        mWavePath.lineTo(getWidth(), getHeight());
        mWavePath.lineTo(0, getHeight());
        mWavePath.close();

        canvas.drawPath(mWavePath, mWavePaint);
    }

    private void drawSinPathTwo(Canvas canvas) {
        //mWavePathTwo.reset();
    
        mWavePathTwo.moveTo(mScreenWidth, -mWaveLength + mOffset);
        
        for (int i = 0; i < mWaveCount; i++) {
            mWavePathTwo.quadTo(mScreenWidth, -mWaveLength * 3 / 4 + mOffset + i * mWaveLength, mScreenWidth -mWaveAmplitude,  -mWaveLength / 2 + mOffset + i * mWaveLength);

            mWavePathTwo.quadTo(mScreenWidth - 2*mWaveAmplitude, -mWaveLength / 4 + mOffset + i * mWaveLength, mScreenWidth,  mOffset + i * mWaveLength);
        }        
        
        mWavePathTwo.lineTo(getWidth(), getHeight());
        mWavePathTwo.lineTo(getHeight(), 0);
        mWavePathTwo.close();

        canvas.drawPath(mWavePathTwo, mWavePaint);
        
    }
    
    /**
     * cos函数图像的波形
     *
     * @param canvas
     */
    private void drawCosPath(Canvas canvas) {
        mWavePath.reset();

        mWavePath.moveTo(-mWaveLength + mOffset, mWaveAmplitude);

        for (int i = 0; i < mWaveCount; i++) {

            // 第一个控制点的坐标为(-mWaveLength * 3 / 4,3 * mWaveAmplitude
            mWavePath.quadTo(-mWaveLength * 3 / 4 + mOffset + i * mWaveLength, 3 * mWaveAmplitude,
                    -mWaveLength / 2 + mOffset + i * mWaveLength, mWaveAmplitude);

            // 第二个控制点的坐标为(-mWaveLength / 4,-mWaveAmplitude)
            mWavePath.quadTo(-mWaveLength / 4 + mOffset + i * mWaveLength, -mWaveAmplitude, mOffset + i * mWaveLength,
                    mWaveAmplitude);
        }

        mWavePath.lineTo(getWidth(), getHeight());
        mWavePath.lineTo(0, getHeight());
        mWavePath.close();

        canvas.drawPath(mWavePath, mWavePaint);
    }

    /**
     * 波形动画
     */
    private void initAnimation() {
        valueAnimator = ValueAnimator.ofInt(0, mWaveLength);
        valueAnimator.setDuration(2000);
        valueAnimator.setStartDelay(300);
        valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mOffset = (Integer) animation.getAnimatedValue();
                invalidate();
            }
        });
        valueAnimator.start();
    }

    public void startAnimation() {
        if (valueAnimator == null) {
            initAnimation();
        }
    }

    public void stopAnimation() {
        if (valueAnimator != null) {
            valueAnimator.cancel();
            valueAnimator = null;
        }
    }

    public void pauseAnimation() {
        if (valueAnimator != null) {
            valueAnimator.pause();
        }
    }

    public void resumeAnimation() {
        if (valueAnimator != null) {
            valueAnimator.resume();
        }
    }

    /**
     * dp 2 px
     *
     * @param dpVal
     */
    protected int dp2px(int dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());
    }
}

当然,如果你只要一个波浪运动的话,上面的代码就已经完全可以满足了。不管是横向波浪还是紧向的波浪,都是靠改变quadTo里面的坐标来实现。上面左图的效果,可以看方法drawSinPath 和 drawSinPathTwo。  drawSinPath控制的是左边的波浪,drawSinPathTwo控制的是右边的波浪。方法drawCosPath 右图的横向波浪。

下面来实现来电话并且屏幕向下的时候调用贝塞尔方法。

分析:

1、来电在任何时候都有可能发生,所以必须要要在service中来实现;

2、来电的时候,手机有可能分锁屏和不锁屏的时候,所以要考虑使用WindowManager的addView来实现,并且要用FLAG_SHOW_WHEN_LOCKED的flag,这样就算在锁屏界面也可以让贝塞尔曲线覆盖到来电界面之上。   

3、屏幕向下其实就是重力的z方向。虽然我认为屏幕向下的时候才显示贝塞尔曲线感觉有点傻x,但据说是仿三星的效果。

 

实现:


import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Handler;
import android.os.IBinder;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.FrameLayout;

public class ComingCallService extends Service {
    private FrameLayout mFl;
    private WindowManager wm;
    private View mFloatView;
    private WindowManager.LayoutParams mLayoutParams;
    private TelephonyManager mTelephony;

    private WaveViewByBezier mView;

    private SensorManager sm;
    private Sensor sensor;
    private SensorEventListener mySensorListener;
    private float mSensorOrientation = 0;
    private int mPhoneSate;

    private Handler mHandler = new Handler();

    Runnable run = new Runnable() {
        @Override
        public void run() {
            mFl.setVisibility(View.VISIBLE);
            mView.startAnimation();
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
        mFloatView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.main_view, null);
        mView = (WaveViewByBezier) mFloatView.findViewById(R.id.wave);
        mFl = (FrameLayout) mFloatView.findViewById(R.id.fl);

        mLayoutParams = new WindowManager.LayoutParams();
        mLayoutParams.type = LayoutParams.TYPE_SYSTEM_OVERLAY;
        mLayoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_SHOW_WHEN_LOCKED
                | LayoutParams.FLAG_LAYOUT_IN_SCREEN | LayoutParams.FLAG_LAYOUT_NO_LIMITS;

        mLayoutParams.gravity = Gravity.END | Gravity.TOP;

        wm.addView(mFloatView, mLayoutParams);

        mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
        mTelephony.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);

        accelermeter();
    }

    private void accelermeter() {
        sm = (SensorManager) getSystemService(Service.SENSOR_SERVICE);
        sensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        mySensorListener = new SensorEventListener() {

            @Override
            public void onSensorChanged(SensorEvent event) {
                mSensorOrientation = event.values[2];
                if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                    if (mPhoneSate == TelephonyManager.CALL_STATE_RINGING && mSensorOrientation < 0) {
                        mHandler.post(run);
                    } else {
                        mFl.setVisibility(View.GONE);
                        mView.stopAnimation();
                    }
                }
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {
            }
        };
        sm.registerListener(mySensorListener, sensor, SensorManager.SENSOR_DELAY_GAME);
                                                                                            
    }

    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
            mPhoneSate = state;

            super.onCallStateChanged(state, incomingNumber);
        }
    };

    @Override
    public void onDestroy() {
        sm.unregisterListener(mySensorListener);
    }
}

 

main_view的xml文件

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/fl"
    android:visibility="gone"
    android:background="#000000" >

    <com.lmkj.bezier.WaveViewByBezier
        android:id="@+id/wave"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</FrameLayout>

最后加一个开机监听广播,只要手机一开机就启动这个service


        <receiver android:name="com.lmkj.bezier.ComingCallBootReceiver" >
            <intent-filter android:priority="1000">
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

 

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class ComingCallBootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {

            Intent mIntent = new Intent(context, ComingCallService.class);
            context.startService(mIntent);
    }

}

权限

    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />

 

完成!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值