看到了好几个开源的水波纹控件,整理一下,以防以后需要。
核心原理:这个控件是个FrameLayout,准备一张半透明波浪图片,先把Layout的子View绘出来,再接着就是通过波浪图片反复绘制覆盖这个控件的子View。
效果图:
这个控件代码不多,核心控件代码贴出来,省的导入工程麻烦
package com.dk.view.sinking;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.FrameLayout;
public class SinkingView extends FrameLayout {
private static final int DEFAULT_TEXTCOLOT = 0xff0074a2;
private static final int DEFAULT_TEXTSIZE = 32;
private float mPercent;
private Paint mPaint = new Paint();
private Bitmap mBitmap;
private Bitmap mScaledBitmap;
private float mLeft, mTop;
private int mSpeed = 10;
private int mRepeatCount = 0;
private Status mFlag = Status.NONE;
private int mTextColot = DEFAULT_TEXTCOLOT;
private int mTextSize = DEFAULT_TEXTSIZE;
public SinkingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setTextColor(int color) {
mTextColot = color;
}
public void setTextSize(int size) {
mTextSize = size;
}
public void setPercent(float percent) {
mFlag = Status.RUNNING;
mPercent = percent;
postInvalidate();
}
public void setStatus(Status status) {
mFlag = status;
}
public void clear() {
mFlag = Status.NONE;
if (mScaledBitmap != null) {
mScaledBitmap.recycle();
mScaledBitmap = null;
}
if (mBitmap != null) {
mBitmap.recycle();
mBitmap = null;
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mFlag == Status.RUNNING) {
if (mScaledBitmap == null) {
mBitmap = BitmapFactory.decodeResource(getContext().getResources(), R.drawable.wave);
mScaledBitmap = Bitmap.createScaledBitmap(mBitmap, mBitmap.getWidth(), getHeight(), false);
mBitmap.recycle();
mBitmap = null;
mRepeatCount = (int) Math.ceil(getWidth() / mScaledBitmap.getWidth() + 0.5) + 1;
}
for (int idx = 0; idx < mRepeatCount; idx++) {
canvas.drawBitmap(mScaledBitmap, mLeft + (idx - 1) * mScaledBitmap.getWidth(), -mPercent * getHeight(), null);
}
String str = (int) (mPercent * 100) + "%";
mPaint.setColor(mTextColot);
mPaint.setTextSize(mTextSize);
canvas.drawText(str, (getWidth() - mPaint.measureText(str)) / 2, getHeight() / 2 + mTextSize / 2, mPaint);
mLeft += mSpeed;
if (mLeft >= mScaledBitmap.getWidth())
mLeft = 0;
postInvalidateDelayed(20);
}
}
public enum Status {
RUNNING, NONE
}
}
核心原理:这个控件是个FrameLayout,准备一张半透明波浪图片,先把Layout的子View绘出来,再接着就是通过波浪图片反复绘制覆盖这个控件的子View。
dispatchDraw分析:先获取波浪图片,再根据当前控件的高度得到一张缩放后的波浪图,接着根据波浪图片的宽度与控件的宽度来估算横向需要绘制波浪图的次数mRepeatCount,加1是为了在屏幕左边画一个,之后的for循环就是这个波浪图片的绘制了,可以看出是从屏幕外一个波浪图位置开始的,从mLeft += mSpeed可以看出随着绘图的次数增加,整个画图的起点是往右移的,这就是为什么计算横向画图时要+1的原因,当往右移动到波浪图片宽度时,又把起点归零。图片的进度是从下往上的,实现这个效果更好办,把画图的起点往上移就行了。如此简单就实现了波浪效果,不过是个靠图片实现的伪波浪。
波浪图
二、WaveView
核心代码
private void calculatePath() {
mAboveWavePath.reset();
mBlowWavePath.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);
mBlowWavePath.moveTo(left, bottom);
for (float x = 0; x <= mMaxRight; x += X_SPACE) {
y = (float) (mWaveHeight * Math.sin(omega * x + mBlowOffset) + mWaveHeight);
mBlowWavePath.lineTo(x, y);
}
mBlowWavePath.lineTo(right, bottom);
}
设计:画两个正弦波状图,两个颜色,一个比另一个延迟一段,冒充波峰与波谷。如上的omega的值是2*Math.PI/mWaveLength,mWaveLength的值为控件宽度乘以一个常量,这个常量的值决定了有多少个波,为1时整个屏幕只有一个完成的波长。mBlowOffset与mAboveOffset是个稳定增长的变量,为了保证波浪不停变动用的,稳定增长的值就是波浪变动速度。