android 炫酷的水波扩散效果

一道水波向外扩散的效果

这里写图片描述

如果界面只有纯色是看不出效果的

想法来源于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 <cmath>

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等,就可以看到效果。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值