android 动画之水波纹效果ripple
16lz
2021-01-23
按照上面的分析,我们写出以下Circle类来表示一个圆:
private class Circle { private long mCreateTime; public Circle() { this.mCreateTime = System.currentTimeMillis(); } public int getAlpha() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return (int) ((1.0f - percent) * 255); } public float getCurrentRadius() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return mInitialRadius + percent * (mMaxRadius - mInitialRadius); }}
自然而然,在WaveView中,要有一个List来保存当前正在显示的圆:
private List mCircleList = new ArrayList();
我们定义一个start方法,用来启动扩散:
public void start() { if (!mIsRunning) { mIsRunning = true; mCreateCircle.run(); }}private Runnable mCreateCircle = new Runnable() { @Override public void run() { if (mIsRunning) { newCircle(); postDelayed(mCreateCircle, mSpeed); // 每隔mSpeed毫秒创建一个圆 } }};private void newCircle() { long currentTime = System.currentTimeMillis(); if (currentTime - mLastCreateTime < mSpeed) { return; } Circle circle = new Circle(); mCircleList.add(circle); invalidate(); mLastCreateTime = currentTime;}
start方法只是简单的创建了一个圆并添加到了mCircleList中,同时开启了循环创建圆的Runnable,然后通知界面刷新,我们再看看onDraw方法:
protected void onDraw(Canvas canvas) { Iterator iterator = mCircleList.iterator(); while (iterator.hasNext()) { Circle circle = iterator.next(); if (System.currentTimeMillis() - circle.mCreateTime < mDuration) { mPaint.setAlpha(circle.getAlpha()); canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint); } else { iterator.remove(); } } if (mCircleList.size() > 0) { postInvalidateDelayed(10); }}
onDraw方法遍历了每一个Circle,判断Circle的扩散时间是否超过了设定的扩散时间,如果是则移除,如果不是,则计算Circle当前的透明度和半径并绘制出来。我们添加了一个延时刷新来不断重绘界面,以达到连续的波纹扩散效果。
现在运行程序,应该能看到图2中的效果了,不过有点别扭,按常识,水波的间距是越来越大的,如何做到呢?
技巧
要让水波纹的半径非匀速变大,我们只能去修改Circle.getCurrentRadius()方法了。我们再次看看这个方法:
public float getCurrentRadius() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return mInitialRadius + percent * (mMaxRadius - mInitialRadius);}
percent表示Circle当前扩散时间和总扩散时间的一个百分比,考虑到当前扩散时间超过总扩散时间时Circle会被移除,因此percent的实际区间为[0, 1],看到[0, 1],我不知道大家想到的是什么,我首先想到的就是差值器(Interpolator),我们可以通过定义差值器来实现对Circle半径变化的控制!
我们修改代码:
private Interpolator mInterpolator = new LinearInterpolator();public void setInterpolator(Interpolator interpolator) { mInterpolator = interpolator; if (mInterpolator == null) { mInterpolator = new LinearInterpolator(); }}private class Circle { private long mCreateTime; public Circle() { this.mCreateTime = System.currentTimeMillis(); } public int getAlpha() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255); } public float getCurrentRadius() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius); }}
这样,外部使用WaveView时,只需调用setInterpolator()来定义不同的插值器即可实现不同的效果。
图3效果的代码:
mWaveView = (WaveView) findViewById(R.id.wave_view);mWaveView.setDuration(5000);mWaveView.setStyle(Paint.Style.STROKE);mWaveView.setSpeed(400);mWaveView.setColor(Color.parseColor("#ff0000"));mWaveView.setInterpolator(new AccelerateInterpolator(1.2f));mWaveView.start();
图4效果的代码:
mWaveView = (WaveView) findViewById(R.id.wave_view);mWaveView.setDuration(5000);mWaveView.setStyle(Paint.Style.FILL);mWaveView.setColor(Color.parseColor("#ff0000"));mWaveView.setInterpolator(new LinearOutSlowInInterpolator());mWaveView.start();
附上WaveView的所有代码:
/** * 水波纹特效 * Created by hackware on 2016/6/17. */public class WaveView extends View { private float mInitialRadius; // 初始波纹半径 private float mMaxRadiusRate = 0.85f; // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate; private float mMaxRadius; // 最大波纹半径 private long mDuration = 2000; // 一个波纹从创建到消失的持续时间 private int mSpeed = 500; // 波纹的创建速度,每500ms创建一个 private Interpolator mInterpolator = new LinearInterpolator(); private List mCircleList = new ArrayList(); private boolean mIsRunning; private boolean mMaxRadiusSet; private Paint mPaint; private long mLastCreateTime; private Runnable mCreateCircle = new Runnable() { @Override public void run() { if (mIsRunning) { newCircle(); postDelayed(mCreateCircle, mSpeed); } } }; public WaveView(Context context) { this(context, null); } public WaveView(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); setStyle(Paint.Style.FILL); } public void setStyle(Paint.Style style) { mPaint.setStyle(style); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (!mMaxRadiusSet) { mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f; } } public void setMaxRadiusRate(float maxRadiusRate) { this.mMaxRadiusRate = maxRadiusRate; } public void setColor(int color) { mPaint.setColor(color); } /** * 开始 */ public void start() { if (!mIsRunning) { mIsRunning = true; mCreateCircle.run(); } } /** * 停止 */ public void stop() { mIsRunning = false; } protected void onDraw(Canvas canvas) { Iterator iterator = mCircleList.iterator(); while (iterator.hasNext()) { Circle circle = iterator.next(); if (System.currentTimeMillis() - circle.mCreateTime < mDuration) { mPaint.setAlpha(circle.getAlpha()); canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint); } else { iterator.remove(); } } if (mCircleList.size() > 0) { postInvalidateDelayed(10); } } public void setInitialRadius(float radius) { mInitialRadius = radius; } public void setDuration(long duration) { this.mDuration = duration; } public void setMaxRadius(float maxRadius) { this.mMaxRadius = maxRadius; mMaxRadiusSet = true; } public void setSpeed(int speed) { mSpeed = speed; } private void newCircle() { long currentTime = System.currentTimeMillis(); if (currentTime - mLastCreateTime < mSpeed) { return; } Circle circle = new Circle(); mCircleList.add(circle); invalidate(); mLastCreateTime = currentTime; } private class Circle { private long mCreateTime; public Circle() { this.mCreateTime = System.currentTimeMillis(); } public int getAlpha() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255); } public float getCurrentRadius() { float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration; return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius); } } public void setInterpolator(Interpolator interpolator) { mInterpolator = interpolator; if (mInterpolator == null) { mInterpolator = new LinearInterpolator(); } }}
原博文地址链接:https://www.jianshu.com/p/cba46422de67
更多相关文章
- Android实现自定义的 时间日期 控件
- android粗略获得程序运行时间的方法
- android 开发 时间选择器TimePicker的使用
- CardView 设置水波纹效果
- Android如何获取APP启动时间
- Android中的网络时间同步