一道水波向外扩散的效果
如果界面只有纯色是看不出效果的
想法来源于http://blog.csdn.net/johnsonblog/article/details/7975641
博客中提到的水波效果在手机上很卡,这里再给做了很多减法。直接生成一道水波,只会向外扩散,不去计算每次波能缓冲区域,以及忽略波是怎么干涉,相互影响的。
按函数y=cosπ/2x函数图,在[-1,1]区间形成波,在0处达到最强。java代码如下
protected Bitmap bitmap;
protected int width, height;
float x0, y0;
float len = 10;//波作用的幅度,波长,或叫波宽
float r = 20;//波扩散距离,即平均半径
float weight = 6;//波幅,即水波的最大高度
//初始值
float len0 = 10;//波作用的幅度,波长
float r0 = 20;//波扩散距离
float weight0 = 6;//波幅
public SingleWaterWave(Bitmap bitmap) {
width = bitmap.getWidth();
height = bitmap.getHeight();
int[] b1 = new int[width * height];
bitmap.getPixels(b1, 0, width, 0, 0, width, height);
this.bitmap = bitmap;
int min = width;
len0 = min * 0.02f;
r0 = min * 0.05f;
weight0 = min * 0.015f;
}
//波形渲染
void rippleRender() {
int offset;
int i = 0;
int length = width * height;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++, i++) {
//像素点到波心的距离
double disance = Math.sqrt((x0 - x) * (x0 - x) + (y0 - y) * (y0 - y));
if (disance < r - len || disance > r + len) {//不在水波影响范围
b2[i] = 0x00ffffff;//透明像素
continue;
}
double d = (disance - r) / len;//这个值在区间内由-1变到0再到1
//在[-1,1]区间,形成水波曲线函数,
d = Math.cos(d * Math.PI / 2) * -weight;//向外的像素偏移值;
int dx =(int)( d * (x - x0) / r);//x方向的偏移值
int dy =(int)( d * (y - y0) / r);//y方向的偏移值
// 计算出偏移象素和原始象素的内存地址偏移量 :
offset = dy * width + dx;
// 判断坐标是否在范围内
if (i + offset > 0 && i + offset < length) {
b2[i] = b1[i + offset];
} else {
b2[i] = 0x00ffffff;////透明像素
}
}
}
}
//波形传播 扩散的方法仅仅是半径增加,,波幅减弱,,波长稍加
void rippleSpread() {
//weight -= dw;
//if (weight <= 0) {
// end();
// return;
//}
//len += dlen;
}
计算渲染后的画面主要的耗时就在rippleRender波形渲染这里,用NDK去实现这段代码性能就会好很多
public class JNIRender {
//调用C++的本地方法
public static native void render(int[] b1, int[] b2, int width, int height,
double r,double len, double weight, double x0, double y0);
}
JNI里面我是照搬过去的
#include "com_example_user_mc_JNIRender.h"
#include "../../../../../../android/ndk/android-ndk32-r10-windows-x86_64/android-ndk-r10/toolchains/mipsel-linux-android-4.8/prebuilt/windows-x86_64/lib/gcc/mipsel-linux-android/4.8/include/stddef.h"
#include
void JNICALL Java_com_example_user_mc_JNIRender_render(JNIEnv *env, jclass jclass1, jintArray b1,
jintArray b2, jint width, jint height,
jdouble r, jdouble len, jdouble weight,
jdouble x0,
jdouble y0) {
jint *b3 = env->GetIntArrayElements(b1, false);
jint size = env->GetArrayLength(b1);
jint *b4 = env->GetIntArrayElements(b2, false);
int offset;
int i = 0;
int length = width * height;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++, i++) {
double disance = sqrt((x0 - x) * (x0 - x) + (y0 - y) * (y0 - y));
if (disance < r - len || disance > r + len) {//不在水波影响范围
b4[i] = 0x00ffffff;
continue;
}
double d = (disance - r) / len;//这个值在区间内由-1变到0再到1
//在[-1,1]区间,形成水波曲线函数,
d = cos(d * M_PI / 2) * -weight;//向外的像素偏移值;
int dx =(int)( d * (x - x0) / r);//x方向的偏移值
int dy =(int)( d * (y - y0) / r);//y方向的偏移值
offset = dy * width + dx;// 计算出偏移象素和原始象素的内存地址偏移量 :
// 判断坐标是否在范围内
if (i + offset > 0 && i + offset < length) {
b4[i] = b3[i + offset];
} else {
b4[i] = 0x00ffffff;
}
}
}
env->SetIntArrayRegion(b2, 0, size, b4);
}
封装在一次动画ValueAnimator里面,,让动画去渲染(半径增加,强度减弱,波变宽)整个过程的每一帧,同时可以控制时间和动画取消
//丢一颗石头,即形成一圈水波
public void dropStone(float x, float y) {
x0 = x;
y0 = y;
//初始化状态
len = len0;
r = r0;
weight = weight0;
final float maxR = Math.max(width, height);
//如果有上次的动画,取消上次的动画.
if (animator != null && animator.isRunning()) {
animator.cancel();
}
animator = ValueAnimator.ofFloat(0, 1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float v = (Float) animation.getAnimatedValue();
r = (maxR - r0) * v + r0;
weight = weight0 - weight0 * v;
len = len0 * v + len0;
//调用C++获取b2数组
JNIRender.render(b1, b2, width, height, r, len, weight, x0, y0);
//Animator动画计算出来的帧,转成图片,回调给调用者控件,这样控件可以实时地显示出来
if (l != null) {
if (bitmap != null) {
bitmap.recycle();
}
bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
bitmap.setPixels(b2, 0, width, 0, 0, width, height);
l.onFrame(bitmap);
}
}
});
animator.addListener(new SimpleAnimListener() {
@Override
public void onAnimationEnd(Animator animation) {
if (l != null) {
l.onEnd();
}
}
});
animator.setDuration(2000);
animator.start();
}
SingleWaterWave.java
以上SingleWaterWave的功能只是实现不停渲染水波的每一帧。
然后需要一个水波显示容器,用来显示水波的控件。
控件封装到一个自定义控件FrameLayout 中,在手势事件抬起的时候,放射出一道波,水波覆盖在FrameLayout 的子控件的上层。封装成FrameLayout,这样就可以在里面随便放什么都可以。目前只试过全屏大小的的FrameLayout。
package com.example.user.mytest;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import com.example.user.mc.SingleWaterWave;
/**
* Created by user on 2016/12/2.
*/
public class WaterFrameLayout extends FrameLayout implements SingleWaterWave.OnFrameCreateListener {
private Rect rect;
public WaterFrameLayout(Context context) {
super(context);
init();
}
public WaterFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
void init() {
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (rect == null) {
rect = new Rect();
}
rect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (waveBitmap != null)
canvas.drawBitmap(waveBitmap, null, rect, null);
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
if ((e.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP) {
float ex = e.getX();
float ey = e.getY();
wave(ex, ey);
}
super.dispatchTouchEvent(e);
return true;
}
public void wave(float ex, float ey) {
//取控件绘制的缓存图像
buildDrawingCache();
Bitmap bitmap = getDrawingCache();
Matrix matrix = new Matrix();
float scale = 360f / getWidth();
if (scale > 1) {
scale = 1;
}
matrix.setScale(scale, scale);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, rect.right, rect.bottom, matrix, false);
//销毁控件绘制的缓存图像
destroyDrawingCache();
SingleWaterWave waterWave = new SingleWaterWave(bitmap);
waterWave.setOnFrameCreateListener(this);
int x = (int) (ex * bitmap.getWidth() / getWidth());
int y = (int) (ey * bitmap.getHeight() / getHeight());
waterWave.dropStone(x, y);
}
private Bitmap waveBitmap;
@Override
public void onFrame(Bitmap bitmap) {
waveBitmap = bitmap;
invalidate();
}
@Override
public void onEnd() {
waveBitmap = null;
invalidate();
}
}
怎么用,把这个FrameLayout丢到xml里面,里面放点图片,TextView等,就可以看到效果。