Android water wave effect

Since the recent project needs to achieve a water wave effect similar to the 360 ​​memory monitoring on the Window system. I started looking for a period of time on the Internet and found that there are many similar effects, but they are somewhat different from the effects I want to achieve. So I decided to write one myself (of course, I also borrowed some other people's achievements), which can be used as a study note and help some friends in need. If a friend finds a problem, please feel free to make a decision. Without further ado, go directly to the core code. The complete demo will have a download address at the end:

package com.example.waveanimation;


import java.math.BigDecimal;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.ProgressBar;


/**
 * @author sunjc
 * @Description 
 * @date 2014年12月13日下午3:03:07
 * @version 1.0
 */
public class WaterWaveView extends View {
private Context mContext;
private int mScreenWidth; // View width
private int mScreenHeight; // View height
private int mDefaultWidth = 0; // default width
private Paint mRingPaint;
private Paint mCirclePaint;
private Paint mWavePaint;
private int mRingSTROKEWidth = 30;
private int mCircleSTROKEWidth = 2;
private int mCircleColor = Color.WHITE;
private int mRingColor = Color.BLUE;
private int mWaveColor = Color.RED;

private Handler mHandler;
private boolean mStarted = false;
/ ** Factors that produce a wave effect * /
private long mWaveFactor = 0L;
/ ** Wave speed * /
private final float mWaveSpeed = 0.033F;
/ ** Transparency of water * /
private int mAlpha = 50; // transparency
/ ** Amplitude * /
private float mAmplitude = 23.0F; // 振幅
/ ** Water height as a percentage of the total height of the container * /
private float mWateLevel = 0.4F;// 水高(0~1)
private Path mPath;
/**
* @param context
*/
public WaterWaveView(Context context) {
super(context);
mContext = context;
init(mContext);
}

/**
* @param context
* @param attrs
*/
public WaterWaveView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init(mContext);
}


/**
* @param context
* @param attrs
* @param defStyleAttr
*/
public WaterWaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
init(mContext);
}

private void init(Context context) {
mDefaultWidth = context.getResources().getDisplayMetrics().widthPixels / 2;
mRingPaint = new Paint();
mRingPaint.setColor(mRingColor);
mRingPaint.setStyle(Paint.Style.STROKE);
mRingPaint.setAntiAlias(true);
mRingPaint.setStrokeWidth(mRingSTROKEWidth);

mCirclePaint = new Paint();
mCirclePaint.setColor(mCircleColor);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setAntiAlias(true);
mCirclePaint.setStrokeWidth(mCircleSTROKEWidth);

mWavePaint = new Paint();
mWavePaint.setStrokeWidth(1.0F);
mWavePaint.setColor(mWaveColor);
mWavePaint.setAlpha (mAlpha);
mPath = new Path();

mHandler = new Handler() {
@Override
public void handleMessage(android.os.Message msg) {
if (msg.what == 0) {
invalidate();
if (mStarted) {
// Keep sending messages to yourself so that you are constantly being redrawn
mHandler.sendEmptyMessageDelayed (0, 60L);
}
}
}
};
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = measure(widthMeasureSpec, true);
int height = measure(heightMeasureSpec, false);
if (width < height) {
setMeasuredDimension(width, width);
} else {
setMeasuredDimension(height, height);
}
}

/**
* @category measurement
* @param measureSpec
* @param isWidth
* @return
*/
private int measure(int measureSpec, boolean isWidth) {
int result;

int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
int padding = isWidth ? getPaddingLeft() + getPaddingRight()
: getPaddingTop() + getPaddingBottom();
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
// result = isWidth? getSuggestedMinimumWidth (): getSuggestedMinimumHeight ();
result = mDefaultWidth;
result += padding;
Log.v("TAG","result="+result+",size="+size+"mmode == MeasureSpec.AT_MOST="+(mode == MeasureSpec.AT_MOST));
if (mode == MeasureSpec.AT_MOST) {
if (isWidth) {
result = Math.max(result, size);
} else {
result = Math.min(result, size);
}
}
}
return result;
}

@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mScreenWidth = w;
mScreenHeight = h;
Log.v("TAG","mScreenWidth="+mScreenWidth+",mScreenHeight="+mScreenHeight);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Get the width and height of the control
int width = getWidth();
setBackgroundColor(mContext.getResources().getColor(R.color.green));
float [] angles = getAngles (); // Get the corresponding start angle to end angle of the current wave position in the circle
float startX = getStartX() - mRingSTROKEWidth;
// Wave effect
float endX = mScreenWidth / 2 + mScreenWidth / 2 - startX ;
RectF oval = new RectF(mRingSTROKEWidth, mScreenHeight / 2 - mScreenWidth / 2 + mRingSTROKEWidth, 
mScreenWidth - mRingSTROKEWidth, mScreenHeight / 2 + mScreenWidth / 2 - mRingSTROKEWidth);
// Draw, that is, the height of the water at rest
if(mWateLevel <= 0.5f){
canvas.drawArc(oval, angles[0], angles[1] - angles[0], false, mWavePaint);
}else{
canvas.drawArc(oval, 0 , angles[0] , true , mWavePaint);
canvas.drawArc(oval, angles[1] , 360 - angles[1], true , mWavePaint);
}
canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2,
mScreenWidth / 2 - mRingSTROKEWidth / 2, mRingPaint);
canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 2
- mRingSTROKEWidth, mCirclePaint);
if(mWateLevel > 0.5f){
Path path = new Path();
Paint p = new Paint();
path.moveTo(mScreenWidth / 2.0f, mScreenHeight /2.0f);
path.lineTo(startX + mRingSTROKEWidth, mScreenHeight / 2.0f + mScreenHeight * (0.5f - mWateLevel));
path.lineTo(endX - mRingSTROKEWidth, mScreenHeight / 2.0f + mScreenHeight * (0.5f - mWateLevel));
path.lineTo(mScreenWidth / 2.0f, mScreenHeight /2.0f);
path.close();
p.setStyle(Paint.Style.FILL);
p.setColor(Color.RED);
p.setAlpha(50);
canvas.drawPath(path, p);
}
// If not started (startWave method is not called), draw a sector
if ((!mStarted) || (mScreenWidth == 0) || (mScreenHeight == 0)) {
return;
}
if (this.mWaveFactor >= Integer.MAX_VALUE) {
this.mWaveFactor = 0L;
}
// mWaveFactor will increase every time onDraw
mWaveFactor = (1L + mWaveFactor);
float f1 = mScreenHeight * (1.0F - mWateLevel) - mAmplitude;
float top = f1 + mAmplitude;//
mPath.reset();
while (startX <= endX) {
float startY =(float) (f1 - mAmplitude
* Math.sin(Math.PI * (2.0F * (startX + this.mWaveFactor * width * this.mWaveSpeed)) / width * 1.0f));
if(mWateLevel > 0.5f){
if(startX > mScreenWidth / 2 + mScreenWidth / 2 - getStartX() || startX < getStartX()){
startX++;
continue;
}
}
canvas.drawLine(startX, startY, startX, top, mWavePaint);
startX++;
}
canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2,
mScreenWidth / 2 - mRingSTROKEWidth / 2, mRingPaint);
canvas.drawCircle(mScreenWidth / 2, mScreenHeight / 2, mScreenWidth / 2
- mRingSTROKEWidth, mCirclePaint);
canvas.restore();
}

/**
* Get the position where the current wave starts
* @return
*/
private float getStartX(){
double height = mScreenHeight * Math.abs(0.5f - mWateLevel);
double width = Math.sqrt(Math.pow((mScreenWidth / 2.0f - mRingSTROKEWidth),2) - Math.pow(height, 2));
return (float) (mScreenWidth / 2.0f - width);
}

private float getCricleWidth(){
float height = mScreenHeight * Math.abs(0.5f - mWateLevel);
float width = (float) Math.sqrt(Math.pow((mScreenWidth / 2 - mRingSTROKEWidth),2) - Math.pow(height, 2));
return width;
}

/**
* Get start angle to end angle
* @return
*/
private float[] getAngles(){
float[] angles = new float[2];
double radius = mScreenWidth / 2 - mRingSTROKEWidth;
double height = mScreenHeight * Math.abs(0.5f - mWateLevel);
double width = Math.sqrt(Math.pow(radius,2) - Math.pow(height, 2));
double angle = (Math.acos((Math.pow(height, 2) + Math.pow(radius,2) - 
Math.pow(width, 2)) / (2.0f * height * radius)) / Math.PI ) * 180;
Log.v("TAG","org angle="+angle);
if(mWateLevel < 0.5f){
angles[0] = (float) (90 - angle);
angles[1] = (float) (90 + angle);
}else if(mWateLevel > 0.5f){
angles[0] = (float) (270 - angle);
angles[1] = (float) (270 + angle);
}else{
angles[0] = 0;
angles[1] = 180;
}
return angles;
}

@Override
public Parcelable onSaveInstanceState() {
// Force our ancestor class to save its state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.progress = (int) mWaveFactor;
return ss;
}

@Override
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mWaveFactor = ss.progress;
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
// Turn off hardware acceleration to prevent unsupported operation exception
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}

/**
* Set current display value and total value
* @param curValue
* @param totalValue
*/
public void setWateLevel(float curValue,float totalValue){
if(curValue > totalValue){
return;
}
mWateLevel = curValue / totalValue;
BigDecimal bd = new BigDecimal(mWateLevel);
mWateLevel = bd.setScale(2,BigDecimal.ROUND_HALF_UP).floatValue();  
}

/**
* @category starts to fluctuate
*/
public void startWave() {
if (!mStarted) {
this.mWaveFactor = 0L;
mStarted = true;
this.mHandler.sendEmptyMessage(0);
}
}

/**
* @category stop volatility
*/
public void stopWave() {
if (mStarted) {
this.mWaveFactor = 0L;
mStarted = false;
this.mHandler.removeMessages(0);
}
}

/**
* @category save status
*/
static class SavedState extends BaseSavedState {
int progress;
/**
* Constructor called from {@link ProgressBar#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}

/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
progress = in.readInt();
}

@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(progress);
}

public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}


public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}

   Demo download address

Published 6 original articles · praised 4 · 10,000+ views

Guess you like

Origin blog.csdn.net/sjc53yy/article/details/41910253