Android 水波效果

由于最近项目需要实现一个类似于Window系统上360内存监控的水波效果。开始在网上找一个段时间,发现有很多类似效果,但与自己想实现的效果多少有点出入。所以决心自己来写一个(当然也借鉴了一些别人的成果),既可以当做是一个学习笔记,也帮助一些有需要朋友。如有朋友发现不对的问题,欢迎拍板。废话不多说了,直接上核心代码。完整的demo最后面会有下载地址:

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的宽度
private int mScreenHeight;//view的高度
private int mDefaultWidth = 0;//默认宽度
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;
/** 产生波浪效果的因子 */
private long mWaveFactor = 0L;
/** 波浪的速度 */
private final float mWaveSpeed = 0.033F;
/** 水的透明度 */
private int mAlpha = 50;// 透明度
/** 振幅 */
private float mAmplitude = 23.0F; // 振幅
/** 水的高度占容器总高度的百分比 */
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) {
// 不断发消息给自己,使自己不断被重绘
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 测量
* @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);
// 得到控件的宽高
int width = getWidth();
setBackgroundColor(mContext.getResources().getColor(R.color.green));
float[] angles = getAngles();//获取当前波浪位置在圆中对应的开始角度到结束角度
float startX = getStartX() - mRingSTROKEWidth;
// 波浪效果
float endX = mScreenWidth / 2 + mScreenWidth / 2 - startX ;
RectF oval = new RectF(mRingSTROKEWidth, mScreenHeight / 2 - mScreenWidth / 2 + mRingSTROKEWidth, 
mScreenWidth - mRingSTROKEWidth, mScreenHeight / 2 + mScreenWidth / 2 - mRingSTROKEWidth);
// 绘制,即水面静止时的高度
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);
}
// 如果未开始(未调用startWave方法),绘制一个扇形
if ((!mStarted) || (mScreenWidth == 0) || (mScreenHeight == 0)) {
return;
}
if (this.mWaveFactor >= Integer.MAX_VALUE) {
this.mWaveFactor = 0L;
}
// 每次onDraw时mWaveFactor都会自增
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();
}

/**
* 获取当前波浪开始的位置
* @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;
}

/**
* 获取开始角度到结束角度
* @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();
// 关闭硬件加速,防止异常unsupported operation exception
this.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

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

/**
* 设置当前显示值与总共值
* @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 开始波动
*/
public void startWave() {
if (!mStarted) {
this.mWaveFactor = 0L;
mStarted = true;
this.mHandler.sendEmptyMessage(0);
}
}

/**
* @category 停止波动
*/
public void stopWave() {
if (mStarted) {
this.mWaveFactor = 0L;
mStarted = false;
this.mHandler.removeMessages(0);
}
}

/**
* @category 保存状态
*/
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下载地址

发布了6 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sjc53yy/article/details/41910253