我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android WaveView实现水流波动效果

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android WaveView实现水流波动效果

   水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它来绘制Path曲线。待会儿再讲解2阶的贝塞尔曲线是怎么回事,先来看实现的效果:


这个波长比较短,还看不到起伏,只是荡漾,把波长拉长再看一下:


已经可以看到起伏很明显了,再拉长看一下:


这个的起伏感就比较强了。利用这个波动效果,可以用在绘制水位线的时候使用到,还可以做一个波动的进度条WaveUpProgress,比如这样:


是不是很动感?

那这样的波动效果是怎么做的呢?前面讲到的贝塞尔曲线到底是什么呢?下面一一讲解。想要用好贝塞尔曲线就得先理解它的表达式,为了形象描述,我从网上盗了些动图。

首先看1阶贝塞尔曲线的表达式:

                             

随着t的变化,它实际是一条P0到P1的直线段:

                                

Android中Path的quadTo是3点的2阶贝塞尔曲线,那么2阶的表达式是这样的:

   

看起来很复杂,我把它拆分开来看:

        

然后再合并成这样:

      

看到什么了吧?如果看不出来再替换成这样:

     

      

     

B0和B1分别是P0到P1和P1到P2的1阶贝塞尔曲线。而2阶贝塞尔曲线B就是B0到B1的1阶贝塞尔曲线。显然,它的动态图表示出来就不难理解了:

                                          

红色点的运动轨迹就是B的轨迹,这就是2阶贝塞尔曲线了。当P1位于P0和P2的垂直平分线上时,B就是开口向上或向下的抛物线了。而在WaveView中就是用的开口向上和向下的抛物线模拟水波。在Android里用Path的方法,首先path.moveTo(P0),然后path.quadTo(P1, P2),canvas.drawPath(path, paint)曲线就出来了,如果想要绘制多个贝塞尔曲线就不断的quadTo吧。

    讲完贝塞尔曲线后就要开始讲水波动的效果是怎么来的了,首先要理解,机械波的传输就是通过介质的震动把波形往传输方向平移,每震动一个周期波形刚好平移一个波长,所有介质点又回到一个周期前的状态。所以要实现水波动效果只需要把波形平移就可以了。

那么WaveView的实现原理是这样的:

    首先在View上根据View宽计算可以容纳几个完整波形,不够一个的算一个,然后在View的不可见处预留一个完整的波形;然后波动开始的时候将所有点同时在x方向上移动相同的距离,这样隐藏的波形就会被平移出来,当平移距离达到一个波长时,这时候将所有点的x坐标又恢复到平移前的值,这样就可以一个波形一个波形地往外传输。用草图表示如下:


WaveView的原理在上图很直观的看出来了,P[2n+1],n>=0都是贝塞尔曲线的控制点,红线为水位线。

知道原理以后可以看代码了:

WaveView.java:


package com.jingchen.waveview; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Timer; 
import java.util.TimerTask; 
import android.content.Context; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.Paint.Align; 
import android.graphics.Paint.Style; 
import android.graphics.Region.Op; 
import android.graphics.Path; 
import android.graphics.RectF; 
import android.os.Handler; 
import android.os.Message; 
import android.util.AttributeSet; 
import android.view.View; 
 
public class WaveView extends View 
{ 
 private int mViewWidth; 
 private int mViewHeight; 
  
 private float mLevelLine; 
  
 private float mWaveHeight = 80; 
  
 private float mWaveWidth = 200; 
  
 private float mLeftSide; 
 private float mMoveLen; 
  
 public static final float SPEED = 1.7f; 
 private List<Point> mPointsList; 
 private Paint mPaint; 
 private Paint mTextPaint; 
 private Path mWavePath; 
 private boolean isMeasured = false; 
 private Timer timer; 
 private MyTimerTask mTask; 
 Handler updateHandler = new Handler() 
 { 
  @Override 
  public void handleMessage(Message msg) 
  { 
   // 记录平移总位移 
   mMoveLen += SPEED; 
   // 水位上升 
   mLevelLine -= 0.1f; 
   if (mLevelLine < 0) 
    mLevelLine = 0; 
   mLeftSide += SPEED; 
   // 波形平移 
   for (int i = 0; i < mPointsList.size(); i++) 
   { 
    mPointsList.get(i).setX(mPointsList.get(i).getX() + SPEED); 
    switch (i % 4) 
    { 
    case 0: 
    case 2: 
     mPointsList.get(i).setY(mLevelLine); 
     break; 
    case 1: 
     mPointsList.get(i).setY(mLevelLine + mWaveHeight); 
     break; 
    case 3: 
     mPointsList.get(i).setY(mLevelLine - mWaveHeight); 
     break; 
    } 
   } 
   if (mMoveLen >= mWaveWidth) 
   { 
    // 波形平移超过一个完整波形后复位 
    mMoveLen = 0; 
    resetPoints(); 
   } 
   invalidate(); 
  } 
 }; 
  
 private void resetPoints() 
 { 
  mLeftSide = -mWaveWidth; 
  for (int i = 0; i < mPointsList.size(); i++) 
  { 
   mPointsList.get(i).setX(i * mWaveWidth / 4 - mWaveWidth); 
  } 
 } 
 public WaveView(Context context) 
 { 
  super(context); 
  init(); 
 } 
 public WaveView(Context context, AttributeSet attrs) 
 { 
  super(context, attrs); 
  init(); 
 } 
 public WaveView(Context context, AttributeSet attrs, int defStyle) 
 { 
  super(context, attrs, defStyle); 
  init(); 
 } 
 private void init() 
 { 
  mPointsList = new ArrayList<Point>(); 
  timer = new Timer(); 
  mPaint = new Paint(); 
  mPaint.setAntiAlias(true); 
  mPaint.setStyle(Style.FILL); 
  mPaint.setColor(Color.BLUE); 
  mTextPaint = new Paint(); 
  mTextPaint.setColor(Color.WHITE); 
  mTextPaint.setTextAlign(Align.CENTER); 
  mTextPaint.setTextSize(30); 
  mWavePath = new Path(); 
 } 
 @Override 
 public void onWindowFocusChanged(boolean hasWindowFocus) 
 { 
  super.onWindowFocusChanged(hasWindowFocus); 
  // 开始波动 
  start(); 
 } 
 private void start() 
 { 
  if (mTask != null) 
  { 
   mTask.cancel(); 
   mTask = null; 
  } 
  mTask = new MyTimerTask(updateHandler); 
  timer.schedule(mTask, 0, 10); 
 } 
 @Override 
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
 { 
  super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  if (!isMeasured) 
  { 
   isMeasured = true; 
   mViewHeight = getMeasuredHeight(); 
   mViewWidth = getMeasuredWidth(); 
   // 水位线从最底下开始上升 
   mLevelLine = mViewHeight; 
   // 根据View宽度计算波形峰值 
   mWaveHeight = mViewWidth / 2.5f; 
   // 波长等于四倍View宽度也就是View中只能看到四分之一个波形,这样可以使起伏更明显 
   mWaveWidth = mViewWidth * 4; 
   // 左边隐藏的距离预留一个波形 
   mLeftSide = -mWaveWidth; 
   // 这里计算在可见的View宽度中能容纳几个波形,注意n上取整 
   int n = (int) Math.round(mViewWidth / mWaveWidth + 0.5); 
   // n个波形需要4n+1个点,但是我们要预留一个波形在左边隐藏区域,所以需要4n+5个点 
   for (int i = 0; i < (4 * n + 5); i++) 
   { 
    // 从P0开始初始化到P4n+4,总共4n+5个点 
    float x = i * mWaveWidth / 4 - mWaveWidth; 
    float y = 0; 
    switch (i % 4) 
    { 
    case 0: 
    case 2: 
     // 零点位于水位线上 
     y = mLevelLine; 
     break; 
    case 1: 
     // 往下波动的控制点 
     y = mLevelLine + mWaveHeight; 
     break; 
    case 3: 
     // 往上波动的控制点 
     y = mLevelLine - mWaveHeight; 
     break; 
    } 
    mPointsList.add(new Point(x, y)); 
   } 
  } 
 } 
 @Override 
 protected void onDraw(Canvas canvas) 
 { 
  mWavePath.reset(); 
  int i = 0; 
  mWavePath.moveTo(mPointsList.get(0).getX(), mPointsList.get(0).getY()); 
  for (; i < mPointsList.size() - 2; i = i + 2) 
  { 
   mWavePath.quadTo(mPointsList.get(i + 1).getX(), 
     mPointsList.get(i + 1).getY(), mPointsList.get(i + 2) 
       .getX(), mPointsList.get(i + 2).getY()); 
  } 
  mWavePath.lineTo(mPointsList.get(i).getX(), mViewHeight); 
  mWavePath.lineTo(mLeftSide, mViewHeight); 
  mWavePath.close(); 
  // mPaint的Style是FILL,会填充整个Path区域 
  canvas.drawPath(mWavePath, mPaint); 
  // 绘制百分比 
  canvas.drawText("" + ((int) ((1 - mLevelLine / mViewHeight) * 100)) 
    + "%", mViewWidth / 2, mLevelLine + mWaveHeight 
    + (mViewHeight - mLevelLine - mWaveHeight) / 2, mTextPaint); 
 } 
 class MyTimerTask extends TimerTask 
 { 
  Handler handler; 
  public MyTimerTask(Handler handler) 
  { 
   this.handler = handler; 
  } 
  @Override 
  public void run() 
  { 
   handler.sendMessage(handler.obtainMessage()); 
  } 
 } 
 class Point 
 { 
  private float x; 
  private float y; 
  public float getX() 
  { 
   return x; 
  } 
  public void setX(float x) 
  { 
   this.x = x; 
  } 
  public float getY() 
  { 
   return y; 
  } 
  public void setY(float y) 
  { 
   this.y = y; 
  } 
  public Point(float x, float y) 
  { 
   this.x = x; 
   this.y = y; 
  } 
 } 
} 

代码中注释写的很多,不难看懂。
Demo的布局:


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:background="#000000" > 
 <com.jingchen.waveview.WaveView 
  android:layout_width="100dp" 
  android:background="#ffffff" 
  android:layout_height="match_parent" 
  android:layout_centerInParent="true" /> 
</RelativeLayout> 

MainActivity的代码:


package com.jingchen.waveview; 
import android.os.Bundle; 
import android.app.Activity; 
import android.view.Menu; 
public class MainActivity extends Activity 
{ 
 @Override 
 protected void onCreate(Bundle savedInstanceState) 
 { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.activity_main); 
 } 
 @Override 
 public boolean onCreateOptionsMenu(Menu menu) 
 { 
  getMenuInflater().inflate(R.menu.main, menu); 
  return true; 
 } 
} 

代码量很少,这样就可以很简单的做出水波效果啦。

源码下载: 《Android实现水流波动效果》

您可能感兴趣的文章:JS实现很酷的水波文字特效实例基于jQuery实现鼠标点击导航菜单水波动画效果附源码下载Android实现点击Button产生水波纹效果Android项目实战手把手教你画圆形水波纹loadingview


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android WaveView实现水流波动效果

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android WaveView实现水流波动效果

水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它来绘制Path曲线。待会儿再讲
2022-06-06

Android自定义WaveView实现波浪进度效果

实现原理 首先就是自定义个WaveView 继承View,然后再WaveView 内部实现代码逻辑: ① 水波就波嘛? sin函数? 贝塞尔曲线? 都行,这里就用二阶贝塞 尔曲线去画吧 ② 波要动嘛,怎么动呢?线程?
2022-06-06

Android实现水波纹效果

一、效果 点击开始: 点击停止: 二、在MainActivity中import android.graphics.Paint; import android.os.Bundle; import android.support.v4.v
2022-06-06

Android实现水波纹点击效果

Android实现水波纹点击效果只在Android5.0以上版本有效,水波纹点击效果代码供大家参考,具体内容如下圆角背景的水波纹效果(如上图) 1. 定义一个普通圆角背景的xml;rounded_corners.xml
2022-06-06

Android如何实现水波纹效果

这篇文章主要为大家展示了“Android如何实现水波纹效果”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Android如何实现水波纹效果”这篇文章吧。效果图attrs.xml自定义属性
2023-06-29

Android 自定义view实现水波纹动画效果

在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果,兴致高昂的来找你,看了之后目的很明确,当然就是希望你能给她;在这样的关键时候,身子板就一定得硬了,可千万别说不行,爷们儿怎么能说不行呢;好了
2023-05-31

Android实现渐变色水波纹效果

本文实例为大家分享了Android实现渐变色水波纹效果的具体代码,供大家参考,具体内容如下 项目中使用到的效果,效果图如下:代码实现:public class WaveView extends View {private Paint mPa
Android实现渐变色水波纹效果
2022-06-07

CSS如何实现波动水球效果

这篇文章将为大家详细讲解有关CSS如何实现波动水球效果,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。今天学习到了一个新的css特效,波动水球效果,也是非常的好看HTML:
2023-06-08

Android自定义View实现水波纹效果

介绍:水波纹散开效果的控件在 App 里面还是比较常见的,例如 网易云音乐歌曲识别,附近搜索场景。看下实现的效果:实现思路: 先将最大圆半径与最小圆半径间距分成几等份,从内到外,Paint 透明度依次递减,绘制出同心圆,然后不断的改变这些同
2023-05-30

Android自定义View 实现水波纹动画引导效果

一、实现效果图二、实现代码 1.自定义viewpackage com.czhappy.showintroduce.view; import android.content.Context; import android.graphics.B
2022-06-06

Android实现点击Button产生水波纹效果

先上图,看看接下来我要向大家介绍的是个什么东西,如下图: 接下来要介绍的就是如何实现上述图中的波纹效果,这种效果如果大家没有体验过的话,可以看看百度手机卫士或者360手机卫士,里面的按钮点击效果都是这样的,另外Android 5.0以上的版
2022-06-06

Android如何实现渐变色水波纹效果

这篇文章主要介绍了Android如何实现渐变色水波纹效果,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。项目中使用到的效果,效果图如下:代码实现:public class Wa
2023-06-21

Android实现自定义华丽的水波纹效果

先来看看效果实现效果模拟水波纹的效果:点击屏幕就有圆环出现,半径从小到大,透明度从大到小(0为透明) 实现思路 1.自定义类继承View。 2.定义每个圆环的实体类 Wave,并初始化绘制圆环的画笔的数据。 3
2022-06-06

Android特效之水波纹的实现

前言 水波纹特效,想必大家或多或少见过,在我的印象中,大致有如下几种: 支付宝 "咻咻咻" 式 流量球 "荡漾" 式 真实的水波纹效果,基于Bitmap处理式话不多说,先来看看效果: 填充式水波纹,间距相等非填充式
2022-06-06

Android自定义view实现水波纹进度球效果

今天我们要实现的这个view没有太多交互性的view,所以就继承view。 自定义view的套路,套路很深 1、获取我们自定义属性attrs(可省略) 2、重写onMeasure方法,计算控件的宽和高 3、重
2022-06-06

Vue怎么实现加水波纹效果

本篇内容主要讲解“Vue怎么实现加水波纹效果”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue怎么实现加水波纹效果”吧!自定义指令指令的作用言简意赅,就是操作底层dom当然vue自身有非常强大
2023-06-29

css3+javascript按钮水波纹效果怎么实现

小编给大家分享一下css3+javascript按钮水波纹效果怎么实现,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!css3+js实现按钮水纹涟漪效果HTML首先我们用标签定义两个按钮butt

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录