刚做的一个项目当中,加了一个功能,来电话,并且屏幕向下的时候,做一个动画效果。效果如下:
这是静态图片,动起来的时候就是波浪运动效果。
先上贝塞尔的实现(我这个是网上找的)
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" />
完成!