res/values/attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="WaveView">
<attr name="above_wave_color" format="color"/>
<attr name="blow_wave_color" format="color"/>
<attr name="progress" format="integer"/>
<attr name="wave_length" format="enum">
<enum name="large" value="1"/>
<enum name="middle" value="2"/>
<enum name="little" value="3"/>
</attr>
<attr name="wave_height" format="enum">
<enum name="large" value="1"/>
<enum name="middle" value="2"/>
<enum name="little" value="3"/>
</attr>
<attr name="wave_hz" format="enum">
<enum name="large" value="1"/>
<enum name="middle" value="2"/>
<enum name="little" value="3"/>
</attr>
</declare-styleable>
</resources>
WaveView.java
public class WaveView extends LinearLayout {
protected static final int LARGE = 1;
protected static final int MIDDLE = 2;
protected static final int LITTLE = 3;
private final int DEFAULT_ABOVE_WAVE_COLOR = Color.WHITE;
private final int DEFAULT_BLOW_WAVE_COLOR = Color.WHITE;
private final int DEFAULT_PROGRESS = 80;
private int mAboveWaveColor;
private int mBellowWaveColor;
private int mProgress;
private int mWaveHeight;
private int mWaveMultiple;
private int mWaveHz;
private int mWaveToTop;
private Wave mWave;
private Solid mSolid;
public WaveView(Context context) {
this(context, null);
}
public WaveView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOrientation(VERTICAL);
final TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WaveView, R.attr.waveViewStyle, 0);
mAboveWaveColor = attributes.getColor(R.styleable.WaveView_above_wave_color, DEFAULT_ABOVE_WAVE_COLOR);
mBellowWaveColor = attributes.getColor(R.styleable.WaveView_blow_wave_color, DEFAULT_BLOW_WAVE_COLOR);
mProgress = attributes.getInt(R.styleable.WaveView_progress, DEFAULT_PROGRESS);
mWaveHeight = attributes.getInt(R.styleable.WaveView_wave_height, MIDDLE);
mWaveMultiple = attributes.getInt(R.styleable.WaveView_wave_length, LARGE);
mWaveHz = attributes.getInt(R.styleable.WaveView_wave_hz, MIDDLE);
attributes.recycle();
mWave = new Wave(context, null);
mWave.initializeWaveSize(mWaveMultiple, mWaveHeight, mWaveHz);
mWave.setAboveWaveColor(mAboveWaveColor);
mWave.setBellowWaveColor(mBellowWaveColor);
mWave.initializePainters();
mSolid = new Solid(context, null);
mSolid.setAboveWavePaint(mWave.getAboveWavePaint());
mSolid.setBellowWavePaint(mWave.getBellowWavePaint());
addView(mWave);
addView(mSolid);
setProgress(mProgress);
}
public void setProgress(int progress) {
mProgress = progress > 100 ? 100 : progress;
computeWaveToTop();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
computeWaveToTop();
}
}
private void computeWaveToTop() {
mWaveToTop = (int) (getHeight() * (1f - mProgress / 100f));
ViewGroup.LayoutParams lp = mWave.getLayoutParams();
if (lp != null) {
((LayoutParams) lp).topMargin = mWaveToTop;
}
mWave.setLayoutParams(lp);
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.progress = mProgress;
return savedState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState savedState = (SavedState) state;
super.onRestoreInstanceState(savedState.getSuperState());
setProgress(savedState.progress);
}
private static class SavedState extends BaseSavedState {
int progress;
public SavedState(Parcel source) {
super(source);
progress = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(progress);
}
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
Wave.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.kimwong.dialogfragmentdemo.R;
/**
* y=Asin(ωx+φ)+k
*/
class Wave extends View {
private final int WAVE_HEIGHT_LARGE = 16;
private final int WAVE_HEIGHT_MIDDLE = 8;
private final int WAVE_HEIGHT_LITTLE = 5;
private final float WAVE_LENGTH_MULTIPLE_LARGE = 1.5f;
private final float WAVE_LENGTH_MULTIPLE_MIDDLE = 1f;
private final float WAVE_LENGTH_MULTIPLE_LITTLE = 0.5f;
private final float WAVE_HZ_FAST = 0.13f;
private final float WAVE_HZ_NORMAL = 0.09f;
private final float WAVE_HZ_SLOW = 0.05f;
public final int DEFAULT_ABOVE_WAVE_ALPHA = 50;
public final int DEFAULT_BLOW_WAVE_ALPHA = 30;
private final float X_SPACE = 20;
private final double PI2 = 2 * Math.PI;
private Path mAboveWavePath = new Path();
private Path mBellowWavePath = new Path();
private Paint mAboveWavePaint = new Paint();
private Paint mBellowWavePaint = new Paint();
private int mAboveWaveColor;
private int mBellowWaveColor;
private float mWaveMultiple;
private float mWaveLength;
private int mWaveHeight;
private float mMaxRight;
private float mWaveHz;
// wave animation
private float mAboveOffset = 0.0f;
private float mBellowOffset;
private RefreshProgressRunnable mRefreshProgressRunnable;
private int left, right, bottom;
// ω
private double omega;
public Wave(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.waveViewStyle);
}
public Wave(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mBellowWavePath, mBellowWavePaint);
canvas.drawPath(mAboveWavePath, mAboveWavePaint);
}
public void setAboveWaveColor(int aboveWaveColor) {
this.mAboveWaveColor = aboveWaveColor;
}
public void setBellowWaveColor(int bellowWaveColor) {
this.mBellowWaveColor = bellowWaveColor;
}
public Paint getAboveWavePaint() {
return mAboveWavePaint;
}
public Paint getBellowWavePaint() {
return mBellowWavePaint;
}
public void initializeWaveSize(int waveMultiple, int waveHeight, int waveHz) {
mWaveMultiple = getWaveMultiple(waveMultiple);
mWaveHeight = getWaveHeight(waveHeight);
mWaveHz = getWaveHz(waveHz);
mBellowOffset = mWaveHeight * 0.4f;
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
mWaveHeight * 2);
setLayoutParams(params);
}
public void initializePainters() {
mAboveWavePaint.setColor(mAboveWaveColor);
mAboveWavePaint.setAlpha(DEFAULT_ABOVE_WAVE_ALPHA);
mAboveWavePaint.setStyle(Paint.Style.FILL);
mAboveWavePaint.setAntiAlias(true);
mBellowWavePaint.setColor(mBellowWaveColor);
mBellowWavePaint.setAlpha(DEFAULT_BLOW_WAVE_ALPHA);
mBellowWavePaint.setStyle(Paint.Style.FILL);
mBellowWavePaint.setAntiAlias(true);
}
private float getWaveMultiple(int size) {
switch (size) {
case WaveView.LARGE:
return WAVE_LENGTH_MULTIPLE_LARGE;
case WaveView.MIDDLE:
return WAVE_LENGTH_MULTIPLE_MIDDLE;
case WaveView.LITTLE:
return WAVE_LENGTH_MULTIPLE_LITTLE;
}
return 0;
}
private int getWaveHeight(int size) {
switch (size) {
case WaveView.LARGE:
return WAVE_HEIGHT_LARGE;
case WaveView.MIDDLE:
return WAVE_HEIGHT_MIDDLE;
case WaveView.LITTLE:
return WAVE_HEIGHT_LITTLE;
}
return 0;
}
private float getWaveHz(int size) {
switch (size) {
case WaveView.LARGE:
return WAVE_HZ_FAST;
case WaveView.MIDDLE:
return WAVE_HZ_NORMAL;
case WaveView.LITTLE:
return WAVE_HZ_SLOW;
}
return 0;
}
/**
* calculate wave track
*/
private void calculatePath() {
mAboveWavePath.reset();
mBellowWavePath.reset();
getWaveOffset();
float y;
mAboveWavePath.moveTo(left, bottom);
for (float x = 0; x <= mMaxRight; x += X_SPACE) {
y = (float) (mWaveHeight * Math.sin(omega * x + mAboveOffset) + mWaveHeight);
mAboveWavePath.lineTo(x, y);
}
mAboveWavePath.lineTo(right, bottom);
mBellowWavePath.moveTo(left, bottom);
for (float x = 0; x <= mMaxRight; x += X_SPACE) {
y = (float) (mWaveHeight * Math.sin(omega * x + mBellowOffset) + mWaveHeight);
mBellowWavePath.lineTo(x, y);
}
mBellowWavePath.lineTo(right, bottom);
}
@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (View.GONE == visibility) {
removeCallbacks(mRefreshProgressRunnable);
} else {
removeCallbacks(mRefreshProgressRunnable);
mRefreshProgressRunnable = new RefreshProgressRunnable();
post(mRefreshProgressRunnable);
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (hasWindowFocus) {
if (mWaveLength == 0) {
startWave();
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mWaveLength==0){
startWave();
}
}
private void startWave() {
if (getWidth() != 0) {
int width = getWidth();
mWaveLength = width * mWaveMultiple;
left = getLeft();
right = getRight();
bottom = getBottom() + 2;
mMaxRight = right + X_SPACE;
omega = PI2 / mWaveLength;
}
}
private void getWaveOffset() {
if (mBellowOffset > Float.MAX_VALUE - 100) {
mBellowOffset = 0;
} else {
mBellowOffset += mWaveHz;
}
if (mAboveOffset > Float.MAX_VALUE - 100) {
mAboveOffset = 0;
} else {
mAboveOffset += mWaveHz;
}
}
private class RefreshProgressRunnable implements Runnable {
public void run() {
synchronized (Wave.this) {
long start = System.currentTimeMillis();
calculatePath();
invalidate();
long gap = 16 - (System.currentTimeMillis() - start);
postDelayed(this, gap < 0 ? 0 : gap);
}
}
}
}
Solid.java
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
class Solid extends View {
private Paint aboveWavePaint;
private Paint bellowWavePaint;
public Solid(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public Solid(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.weight = 1;
setLayoutParams(params);
}
public void setAboveWavePaint(Paint aboveWavePaint) {
this.aboveWavePaint = aboveWavePaint;
}
public void setBellowWavePaint(Paint bellowWavePaint) {
this.bellowWavePaint = bellowWavePaint;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(getLeft(), 0, getRight(), getBottom(), bellowWavePaint);
canvas.drawRect(getLeft(), 0, getRight(), getBottom(), aboveWavePaint);
}
}
using
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.kimwong.dialogfragmentdemo.MainActivity"
tools:showIn="@layout/activity_main">
<com.view.wave.WaveView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff702e8c"
app:above_wave_color="@android:color/white"
app:blow_wave_color="@android:color/white"
app:progress="80"
app:wave_height="little"
app:wave_hz="middle"
app:wave_length="middle"/>
</RelativeLayout>