我的开源页面指示器框架 MagicIndicator,各位一定不要错过哦。

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

  • 支付宝 "咻咻咻" 式
  • 流量球 "荡漾" 式
  • 真实的水波纹效果,基于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();
}
}
}

完整 demo 请访问我的 GitHub

总结


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

小礼物走一走,来简书关注我

Android水波纹特效的简单实现的更多相关文章

  1. WebGL——水波纹特效

    大家好,今天我ccentry要做一个水波纹特效,我们来看看水波纹特效的做法.首先我们来看一下水波纹特效的效果是怎么样的,请看下图. 我们要做的就是类似这种纹理特效,那么我们来看看是如何制作的吧.首先鲫 ...

  2. 2.CCGridAction(3D效果),3D反转特效,凸透镜特效,液体特效,3D翻页特效,水波纹特效,3D晃动的特效,扭曲旋转特效,波动特效,3D波动特效

     1 类图组织 2 实例 CCSprite * spr = CCSprite::create("HelloWorld.png"); spr->setPosition(cc ...

  3. android: Android水波纹点击效果

    Android API 21及以上新增了ripple标签用来实现水波纹的效果.我们可以通过设置ripple背景来实现一些View点击效果. 该水波纹效果有两种:一种是有界的(点击后类似于一个矩形向四周 ...

  4. android 水波纹效果实现

    1.在drawable文件下,新建seletor,作为button的背景,这里我用的是两个圆角的shape <?xml version="1.0" encoding=&quo ...

  5. Android 水波纹点击效果(Ripple Effect)

    上周Android发布了Android M的Preview版本.但想必Android5.0很多炫酷效果,多数开发者还没有使用过,那更不要说广大用户了. 本文介绍的是Android5.0中其中一个炫酷的 ...

  6. CSS 按钮水波纹特效

    /* 按钮反馈之波纹 */ .ripple { position: relative; /* overflow:hidden */  打开注释及效果不扩散在外 } .ripple:focus{ out ...

  7. android 5.0 水波纹 实现

    1. 定义一个普通圆角背景的xml; rounded_corners.xml <?xml version="1.0" encoding="utf-8"?& ...

  8. Android5.0以上的项目都会有的按钮点击特效--水波纹

    <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http: ...

  9. canvas实现水波纹效果

    本文将会从水波的基本原理开始,详细讲解在canvas中模拟水波扩散,分析并计算水波的能量分布,并通过振幅模拟水波对图像的折射效果,最后实现水波特效. 水波基本原理 首先复习一波高中物理知识. 波是指振 ...

随机推荐

  1. 比特币 Bitcoin 是什么,我勒个去,哈耶克果然超前——货币的非国有化,容我思量一下【转载+整理】

    原文地址 比特币矿业史(上):故事的开始,CPU 时代 比特币矿业史(中):群众的觉醒 ,GPU 时代 比特币矿业史(下):巨头的诞生 ,ASIC 时代 本文内容 引子 0 序 1 故事的开始 : C ...

  2. Office办公 SVG的图片文件如何保存为PNG

    用浏览器打开,然后右击图片另存为PNG   再用PS打开可以看到就是没有背景的PNG图片了        

  3. Solidworks如何在零件表面贴图

    在要增加图片的表面上右击,然后选择修改这个面的颜色(可以是曲面)   切换到高级,然后选择一个图片   你可以缩放图片的大小,从而决定图片的重复次数   如果图片上下或者左右颠倒了,可以在映射里面勾选 ...

  4. ZH奶酪:哈工大LTP云平台标记含义及性能

    从官网搬过来的 囧rz 哈工大讯飞语言云 由哈工大 和科大讯飞 联合研发的中文自然语言处理云服务平台.结合了哈工大“语言技术平台——LTP” 高效.精准的自然语言处理核心技术和讯飞公司在全国性大规模云 ...

  5. JavaScript正则式入门

    正则式 正则表达式,又称规则表达式.(英语:Regular Expression,在代码中常简写为regex.regexp或RE),计算机科学的一个概念.正则表通常被用来检索.替换那些符合某个模式(规 ...

  6. 【麦子学院】Linux cmd命令大全

    pwd :print working directory. 打印工作文件夹即当前文件夹. cd :change directory.切换文件夹. /是linux的根文件夹.eg. cd/home ls ...

  7. ArcGIS进行自定义投影转换(重投影)

    这里记录一下使用自定义七参数进行投影转换的过程. 1.主动创建自定义地理(坐标)变换 首先在系统工具箱里面选择创建自定义地理(坐标)变换 在弹出的窗口中输入相关参数即可. 转换方法选择COORDINA ...

  8. centos7 在docker swarm中运行Jenkins,利用gitlab的webhook触发自动部署脚本

    1.宿主机中创建目录 mkdir -p /jenkins_home 2.编辑compose文件,文件名jenkins.yml version: '3.4' services: jenkins-upgr ...

  9. redhat7.0安装ifconfig

    问题描述: Setup is unable to find the "ifconfig" program on your machine. Please make sure it ...

  10. Spring+Quartz实现动态添加定时任务

    发布时间:2018-12-03   技术:spring4.0.2+quartz2.2.1   概述 在最近工作中,由于涉及到定时任务特别多,而这些工作又是由下属去完成的,在生成环境中经常会出现业务逻辑 ...