Android水波纹特效的简单实现

来源:互联网 发布:单片机uart是什么意思 编辑:程序博客网 时间:2024/05/01 11:35


转载地址http://www.jianshu.com/p/cba46422de67


水波纹特效,想必大家或多或少见过,在我的印象中,大致有如下几种:

  • 支付宝 "咻咻咻" 式
  • 流量球 "荡漾" 式
  • 真实的水波纹效果,基于Bitmap处理式

今天我们主要讲一讲如何通过自定义View(以下简称WaveView)实现 "咻咻咻" 式的水波纹扩散效果,少废话,先看东西:


填充式水波纹,间距相等

非填充式水波纹,间距相等

非填充式水波纹,间距不断变大

填充式水波纹,间距不断变小

额,想必大家已经知道基本的原理了,就是用Canvas来画嘛,但可不是简单的画哦,请往下看。

分析


这种类型的水波纹,其实无非就是画圆而已,在给定的矩形中,一个个圆由最小半径扩大到最大半径,伴随着透明度从1.0变为0.0。我们假定这种扩散是匀速的,则一个圆从创建(透明度为1.0)到消失(透明度为0.0)的时长就是定值,那么某一时刻某一个圆的半径以及透明度完全可以由扩散时间(当前时间 - 创建时间)决定。

实现


按照上面的分析,我们写出以下Circle类来表示一个圆:

private class Circle {    private long mCreateTime;    public Circle() {        this.mCreateTime = System.currentTimeMillis();    }    public int getAlpha() {        float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;        return (int) ((1.0f - percent) * 255);    }    public float getCurrentRadius() {        float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;        return mInitialRadius + percent * (mMaxRadius - mInitialRadius);    }}

自然而然,在WaveView中,要有一个List来保存当前正在显示的圆:

private List<Circle> mCircleList = new ArrayList<Circle>();

我们定义一个start方法,用来启动扩散:

public void start() {    if (!mIsRunning) {        mIsRunning = true;        mCreateCircle.run();    }}private Runnable mCreateCircle = new Runnable() {    @Override    public void run() {        if (mIsRunning) {            newCircle();            postDelayed(mCreateCircle, mSpeed); // 每隔mSpeed毫秒创建一个圆        }    }};private void newCircle() {    long currentTime = System.currentTimeMillis();    if (currentTime - mLastCreateTime < mSpeed) {        return;    }    Circle circle = new Circle();    mCircleList.add(circle);    invalidate();    mLastCreateTime = currentTime;}

start方法只是简单的创建了一个圆并添加到了mCircleList中,同时开启了循环创建圆的Runnable,然后通知界面刷新,我们再看看onDraw方法:

protected void onDraw(Canvas canvas) {    Iterator<Circle> iterator = mCircleList.iterator();    while (iterator.hasNext()) {        Circle circle = iterator.next();        if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {            mPaint.setAlpha(circle.getAlpha());            canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);        } else {            iterator.remove();        }    }    if (mCircleList.size() > 0) {        postInvalidateDelayed(10);    }}

onDraw方法遍历了每一个Circle,判断Circle的扩散时间是否超过了设定的扩散时间,如果是则移除,如果不是,则计算Circle当前的透明度和半径并绘制出来。我们添加了一个延时刷新来不断重绘界面,以达到连续的波纹扩散效果。

现在运行程序,应该能看到图2中的效果了,不过有点别扭,按常识,水波的间距是越来越大的,如何做到呢?

技巧


要让水波纹的半径非匀速变大,我们只能去修改Circle.getCurrentRadius()方法了。我们再次看看这个方法:

public float getCurrentRadius() {    float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;    return mInitialRadius + percent * (mMaxRadius - mInitialRadius);}

percent表示Circle当前扩散时间和总扩散时间的一个百分比,考虑到当前扩散时间超过总扩散时间时Circle会被移除,因此percent的实际区间为[0, 1],看到[0, 1],我不知道大家想到的是什么,我首先想到的就是差值器(Interpolator),我们可以通过定义差值器来实现对Circle半径变化的控制!

我们修改代码:

private Interpolator mInterpolator = new LinearInterpolator();public void setInterpolator(Interpolator interpolator) {    mInterpolator = interpolator;    if (mInterpolator == null) {        mInterpolator = new LinearInterpolator();    }}private class Circle {    private long mCreateTime;    public Circle() {        this.mCreateTime = System.currentTimeMillis();    }    public int getAlpha() {        float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;        return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);    }    public float getCurrentRadius() {        float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;        return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);    }}

这样,外部使用WaveView时,只需调用setInterpolator()来定义不同的插值器即可实现不同的效果。

图3效果的代码:

mWaveView = (WaveView) findViewById(R.id.wave_view);mWaveView.setDuration(5000);mWaveView.setStyle(Paint.Style.STROKE);mWaveView.setSpeed(400);mWaveView.setColor(Color.parseColor("#ff0000"));mWaveView.setInterpolator(new AccelerateInterpolator(1.2f));mWaveView.start();

图4效果的代码:

mWaveView = (WaveView) findViewById(R.id.wave_view);mWaveView.setDuration(5000);mWaveView.setStyle(Paint.Style.FILL);mWaveView.setColor(Color.parseColor("#ff0000"));mWaveView.setInterpolator(new LinearOutSlowInInterpolator());mWaveView.start();

附上WaveView的所有代码:

/** * 水波纹特效 * Created by hackware on 2016/6/17. */public class WaveView extends View {    private float mInitialRadius;   // 初始波纹半径    private float mMaxRadiusRate = 0.85f;   // 如果没有设置mMaxRadius,可mMaxRadius = 最小长度 * mMaxRadiusRate;    private float mMaxRadius;   // 最大波纹半径    private long mDuration = 2000; // 一个波纹从创建到消失的持续时间    private int mSpeed = 500;   // 波纹的创建速度,每500ms创建一个    private Interpolator mInterpolator = new LinearInterpolator();    private List<Circle> mCircleList = new ArrayList<Circle>();    private boolean mIsRunning;    private boolean mMaxRadiusSet;    private Paint mPaint;    private long mLastCreateTime;    private Runnable mCreateCircle = new Runnable() {        @Override        public void run() {            if (mIsRunning) {                newCircle();                postDelayed(mCreateCircle, mSpeed);            }        }    };    public WaveView(Context context) {        this(context, null);    }    public WaveView(Context context, AttributeSet attrs) {        super(context, attrs);        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);        setStyle(Paint.Style.FILL);    }    public void setStyle(Paint.Style style) {        mPaint.setStyle(style);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        if (!mMaxRadiusSet) {            mMaxRadius = Math.min(w, h) * mMaxRadiusRate / 2.0f;        }    }    public void setMaxRadiusRate(float maxRadiusRate) {        this.mMaxRadiusRate = maxRadiusRate;    }    public void setColor(int color) {        mPaint.setColor(color);    }    /**     * 开始     */    public void start() {        if (!mIsRunning) {            mIsRunning = true;            mCreateCircle.run();        }    }    /**     * 停止     */    public void stop() {        mIsRunning = false;    }    protected void onDraw(Canvas canvas) {        Iterator<Circle> iterator = mCircleList.iterator();        while (iterator.hasNext()) {            Circle circle = iterator.next();            if (System.currentTimeMillis() - circle.mCreateTime < mDuration) {                mPaint.setAlpha(circle.getAlpha());                canvas.drawCircle(getWidth() / 2, getHeight() / 2, circle.getCurrentRadius(), mPaint);            } else {                iterator.remove();            }        }        if (mCircleList.size() > 0) {            postInvalidateDelayed(10);        }    }    public void setInitialRadius(float radius) {        mInitialRadius = radius;    }    public void setDuration(long duration) {        this.mDuration = duration;    }    public void setMaxRadius(float maxRadius) {        this.mMaxRadius = maxRadius;        mMaxRadiusSet = true;    }    public void setSpeed(int speed) {        mSpeed = speed;    }    private void newCircle() {        long currentTime = System.currentTimeMillis();        if (currentTime - mLastCreateTime < mSpeed) {            return;        }        Circle circle = new Circle();        mCircleList.add(circle);        invalidate();        mLastCreateTime = currentTime;    }    private class Circle {        private long mCreateTime;        public Circle() {            this.mCreateTime = System.currentTimeMillis();        }        public int getAlpha() {            float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;            return (int) ((1.0f - mInterpolator.getInterpolation(percent)) * 255);        }        public float getCurrentRadius() {            float percent = (System.currentTimeMillis() - mCreateTime) * 1.0f / mDuration;            return mInitialRadius + mInterpolator.getInterpolation(percent) * (mMaxRadius - mInitialRadius);        }    }    public void setInterpolator(Interpolator interpolator) {        mInterpolator = interpolator;        if (mInterpolator == null) {            mInterpolator = new LinearInterpolator();        }    }}

总结


想必大家看完这篇文章会觉得原来插值器还可以这么用。其实,有些时候我们使用系统提供的API,往往过于局限其中,有时候换个思路,说不定会得到奇妙的效果。周末愉快~~~。



文/hackware(简书作者)
原文链接:http://www.jianshu.com/p/cba46422de67
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

0 0