In my impression, there are roughly as follows:
- Alipay “swish”
- Flow ball “rippling” type
- Real water ripple effect, based on Bitmap processing
Today we are going to talk about how to create a “swish” effect of water ripples using a custom View (WaveView).
Filled water ripples with equal spacing
Unfilled water ripples, equally spaced
Unfilled water ripple, spacing is increasing
Fill type water ripple, spacing continuously smaller
Well, I think you already know the basic principle, which is to draw with Canvas, but it is not a simple drawing. Please look down.
Analysis of the
This type of water ripple is nothing more than a drawing of circles, which expand from the smallest radius to the largest radius in a given rectangle, along with transparency from 1.0 to 0.0. Assuming that the diffusion is uniform, the time between the creation of a circle (transparency 1.0) and the disappearance of a circle (transparency 0.0) is a fixed value, so the radius and transparency of a circle at a given time can be completely determined by the diffusion time (current time – creation time).
implementation
Following the above analysis, we write the following Circle class to represent a 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); }}Copy the code
Naturally, in WaveView there is a List to hold the circles currently being displayed:
private List mCircleList = new ArrayList();Copy the code
We define a start method to start diffusion:
public void start() { if (! mIsRunning) { mIsRunning = true; mCreateCircle.run(); } } private Runnable mCreateCircle = new Runnable() { @Override public void run() { if (mIsRunning) { newCircle(); postDelayed(mCreateCircle, mSpeed); // Create a circle every mSpeed ms}}}; private void newCircle() { long currentTime = System.currentTimeMillis(); if (currentTime - mLastCreateTime < mSpeed) { return; } Circle circle = new Circle(); mCircleList.add(circle); invalidate(); mLastCreateTime = currentTime; }Copy the code
The start method simply creates a circle and adds it to an mCircleList, turns on the Runnable for circle creation, and tells the interface to refresh.
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); }}Copy the code
The onDraw method iterates over each Circle and determines whether the Circle’s spread time exceeds the set spread time. If so, remove the Circle. If not, calculate the Circle’s current transparency and radius and draw it. We added a delayed refresh to constantly redraw the interface to achieve a continuous ripple effect.
Now when I run the program, I should see the effect shown in Figure 2, but it’s a little awkward.
skills
To make the radius of the water ripple non-uniform, we have to modify the circle.getCurrentradius () method. Let’s look at this method again:
Public float getCurrentRadius() {float Percent = (system.currentTimemillis () -mcreateTime) * 1.0f/mDuration; return mInitialRadius + percent * (mMaxRadius - mInitialRadius); }Copy the code
Percent represents a percentage of the current diffusion time of Circle and the total diffusion time. Considering that Circle will be removed when the current diffusion time exceeds the total diffusion time, the actual interval of percent is [0, 1]. Seeing [0, 1], I don’t know what you think. Interpolator is the first one that comes to mind. We will Interpolator to control the variation of Circle radius!
We modify the code:
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.0 f - mInterpolator. GetInterpolation (percent)) * 255); } public float getCurrentRadius() {float percent = (system.currentTimemillis () -mcreateTime) * 1.0f/mDuration; return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius); }}Copy the code
Thus, when using WaveView externally, setInterpolator() is used to define different interpolators to achieve different effects.
Figure 3 Code for the effect:
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.2 f)); mWaveView.start();Copy the code
Figure 4 Code for the effect:
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();Copy the code
All the code for WaveView is attached:
*/ Public class extends View {private float mInitialRadius; private float mInitialRadius; // Initial ripple radius private float mMaxRadiusRate = 0.85f; // If mMaxRadius is not set, mMaxRadius = minimum length * mMaxRadiusRate; private float mMaxRadius; Private long mDuration = 2000; Private int mSpeed = 500; private int mSpeed = 500; Private Interpolator mInterpolator = new LinearInterpolator(); // Interpolator width = 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); } /** * start */ public void start() {if (! mIsRunning) { mIsRunning = true; mCreateCircle.run(); }} /** * stop */ 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.0 f - 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(); }}}Copy the code
conclusion
After reading this article, you will realize that interpolators can be used in this way. In fact, sometimes we use the API provided by the system, often too limited, sometimes change the way of thinking, may get wonderful results. Have a great weekend.