热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

AndroidWaveView实现水流波动效果

这篇文章主要介绍了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; 
 
/** 
 * 水流波动控件 
 * 
 * @author chenjing 
 * 
 */ 
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 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 = mWaveWidth) 
   { 
    // 波形平移超过一个完整波形后复位 
    mMoveLen = 0; 
    resetPoints(); 
   } 
   invalidate(); 
  } 
 
 }; 
 
 /** 
  * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态 
  */ 
 private void resetPoints() 
 { 
  mLeftSide = -mWaveWidth; 
  for (int i = 0; i (); 
  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 

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

 
 
  
 
 

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实现水流波动效果》

以上就是本文的全部内容,希望对大家学习Android软件编程有所帮助。


推荐阅读
  • 在处理遗留数据库的映射时,反向工程是一个重要的初始步骤。由于实体模式已经在数据库系统中存在,Hibernate 提供了自动化工具来简化这一过程,帮助开发人员快速生成持久化类和映射文件。通过反向工程,可以显著提高开发效率并减少手动配置的错误。此外,该工具还支持对现有数据库结构进行分析,自动生成符合 Hibernate 规范的配置文件,从而加速项目的启动和开发周期。 ... [详细]
  • 在尝试为 Unity 编译一个简单的 Java 库时,运行 `ant jar` 命令后遇到了 Java I/O 异常。具体错误信息为“无法启动程序 ${aAPT},错误代码 2”,这通常表示指定的文件或目录不存在。此问题可能是由于环境配置不正确或路径设置有误导致的。建议检查相关路径和环境变量,确保所有依赖项都已正确安装和配置。 ... [详细]
  • 掌握Android UI设计:利用ZoomControls实现图片缩放功能
    本文介绍了如何在Android应用中通过使用ZoomControls组件来实现图片的缩放功能。ZoomControls提供了一种简单且直观的方式,让用户可以通过点击放大和缩小按钮来调整图片的显示大小。文章详细讲解了ZoomControls的基本用法、布局设置以及与ImageView的结合使用方法,适合初学者快速掌握Android UI设计中的这一重要功能。 ... [详细]
  • 技术分享:深入解析GestureDetector手势识别机制
    技术分享:深入解析GestureDetector手势识别机制 ... [详细]
  • 开发笔记:深入解析Android自定义控件——Button的72种变形技巧
    开发笔记:深入解析Android自定义控件——Button的72种变形技巧 ... [详细]
  • 《精通 jQuery》第六章:深入解析与实战应用
    《精通 jQuery》第六章:深入解析与实战应用本章详细探讨了 Ajax 技术的核心机制及其实际应用。Ajax 通过 XMLHttpRequest 对象实现客户端与服务器之间的异步数据交换,从而在不重新加载整个页面的情况下更新部分内容。这种技术不仅提升了用户体验,还提高了应用的响应速度和效率。此外,本章还介绍了如何利用 jQuery 简化 Ajax 操作,并提供了多个实战案例,帮助读者更好地理解和掌握这一重要技术。 ... [详细]
  • 深入解析 Android 选择器与形状绘制技术
    本文深入探讨了 Android 中选择器(Selector)与形状绘制(Shape Drawing)技术的应用与实现。重点分析了 `Selector` 的 `item` 元素,其中包括 `android:drawable` 属性的使用方法及其在不同状态下的表现。此外,还详细介绍了如何通过 XML 定义复杂的形状和渐变效果,以提升 UI 设计的灵活性和美观性。 ... [详细]
  • 在Maven项目中,高效地切换数据存储库是一项常见的需求。本文介绍了如何通过配置POM文件和使用 profiles 来实现不同环境下的数据存储库切换,确保开发、测试和生产环境的一致性和灵活性。此外,还探讨了最佳实践和常见问题的解决方案,帮助开发者提高工作效率和代码质量。 ... [详细]
  • Go 项目中数据库配置文件的优化与应用 ... [详细]
  • Hadoop 2.6 主要由 HDFS 和 YARN 两大部分组成,其中 YARN 包含了运行在 ResourceManager 的 JVM 中的组件以及在 NodeManager 中运行的部分。本文深入探讨了 Hadoop 2.6 日志文件的解析方法,并详细介绍了 MapReduce 日志管理的最佳实践,旨在帮助用户更好地理解和优化日志处理流程,提高系统运维效率。 ... [详细]
  • 手机号码归属地查询服务由 WebXml.com.cn 提供,通过其 WEB 服务接口(http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx)实现。该服务能够准确解析国内手机号码的归属地信息,适用于多种应用场景,如用户身份验证、市场分析等。 ... [详细]
  • Java Web开发中的JSP:三大指令、九大隐式对象与动作标签详解
    在Java Web开发中,JSP(Java Server Pages)是一种重要的技术,用于构建动态网页。本文详细介绍了JSP的三大指令、九大隐式对象以及动作标签。三大指令包括页面指令、包含指令和标签库指令,它们分别用于设置页面属性、引入其他文件和定义自定义标签。九大隐式对象则涵盖了请求、响应、会话、应用上下文等关键组件,为开发者提供了便捷的操作接口。动作标签则通过预定义的动作来简化页面逻辑,提高开发效率。这些内容对于理解和掌握JSP技术具有重要意义。 ... [详细]
  • 在尝试对从复杂 XSD 生成的类进行序列化时,遇到了 `NullReferenceException` 错误。尽管已经花费了数小时进行调试和搜索相关资料,但仍然无法找到问题的根源。希望社区能够提供一些指导和建议,帮助解决这一难题。 ... [详细]
  • 通过在项目中引用 NuGet 包 `ExcelDataReader`,可以实现高效地读取和导入 Excel 文件中的数据。具体方法是在项目中执行 `Install-Package ExcelDataReader` 命令,然后通过定义一个 `LeadingIn` 方法并传入上传文件的路径来完成数据导入。该方法不仅简化了代码逻辑,还显著提升了数据处理的效率和可靠性。 ... [详细]
  • 在 Vbox 和 Hbox 布局中,当用户点击容器添加一个矩形时,系统会自动为该矩形分配坐标并打印其位置信息。此外,在按键事件触发时,系统仅打印当前矩形的坐标值。这两种布局在特定的交互场景下,能够动态地管理和更新子组件的位置。 ... [详细]
author-avatar
exu8145079
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有