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<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();
  } 

 }; 

 /**
  * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态
  */
 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实现水流波动效果》

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

时间: 2016-03-10

基于jQuery实现鼠标点击导航菜单水波动画效果附源码下载

基于jQuery鼠标点击水波动画竖直导航代码.这是一款基于jQuery+CSS3实现的带动画效果的竖直导航栏特效.效果图如下: 效果展示    源码下载 html代码: <div class="nav"> <ul> <li><a>网站首页</a></li> <li><a>关于我们</a></li> <li><a>产品中心</a>&l

JS实现很酷的水波文字特效实例

本文实例讲述了JS实现很酷的水波文字特效.分享给大家供大家参考.具体实现方法如下: 复制代码 代码如下: <html> <head> <title>JS实现很酷的水波文字效果</title> </head> <body bgcolor="#000000" onLoad="if (document.all)wave()"> <center> <div id='water' sty

Android项目实战手把手教你画圆形水波纹loadingview

本文实例讲解的是如何画一个满满圆形水波纹loadingview,这类效果应用场景很多,比如内存占用百分比之类的,分享给大家供大家参考,具体内容如下 效果图如下: 预备的知识: 1.贝塞尔曲线    如果你不了解,可以来这里进行基础知识储备:神奇的贝塞尔曲线 2.Paint.setXfermode()  以及PorterDuffXfermode 千万不要被这个b的名字吓到,不熟悉看到可能会认为很难记,其实 只要站在巨人的丁丁上 还是很简单的. 好了 废话不多说 ,跟我一步步来做一个炫酷的view吧

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

先上图,看看接下来我要向大家介绍的是个什么东西,如下图: 接下来要介绍的就是如何实现上述图中的波纹效果,这种效果如果大家没有体验过的话,可以看看百度手机卫士或者360手机卫士,里面的按钮点击效果都是这样的,另外Android 5.0以上的版本也出现了这种效果.不多说,下面聊聊具体的怎么实现. 首先大家看到的是三个button,水波纹的出现给我们的错觉是直接将波纹绘制在button上面的,但是这样能做到吗?首先button自己有background和src,如果把半透明的水波纹当作backgrou

js点击按钮实现水波纹效果代码(CSS3和Canves)

近来看到个不错的按钮点击效果,当点击时产生一次水波涟漪效果,挺好玩的,于是简单的实现了下(没考虑低版本浏览器兼容问题) 先看看效果吧,如下图(录制gif软件有点渣,看起来卡卡的...) 这种效果可以由元素内嵌套canves实现,也可以由css3实现. Canves实现  网上摘了一份canves实现的代码,略微去掉了些重复定义的样式并且给出js注释,代码如下 html代码:<a class="btn color-1 material-design" data-color=&quo

Android中Item实现点击水波纹效果

前言 水波纹效果大致上可以分为两类,一类是有界水波纹,而另一类是无界水波纹,较为广泛使用的是有界水波纹. 系统自带的水波纹实现 系统自带的方法非常方便,只需要给相应的空间设置背景,背景内容则为系统自带的 selectableItemBackground ,这样的话,水波纹就会在TextView所在的区域内进行绘制. <TextView android:background="?android:attr/selectableItemBackground" ... /> 先上效

Android实现水波纹效果

一.效果 点击开始: 点击停止: 二.在MainActivity中 import android.graphics.Paint; import android.os.Bundle; import android.support.v4.view.animation.LinearOutSlowInInterpolator; import android.support.v7.app.AppCompatActivity; import android.view.View; import android

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

先来看看效果 实现效果 模拟水波纹的效果:点击屏幕就有圆环出现,半径从小到大,透明度从大到小(0为透明) 实现思路 1.自定义类继承View. 2.定义每个圆环的实体类 Wave,并初始化绘制圆环的画笔的数据. 3.重写onTouchEvent方法,down时,获得坐标点,做为圆环圆心. 4.发送handler信息,对数据进行修改,刷新页面. 5.重写onDraw方法,绘制一个圆环. 1. 自定义类继承View 新建WaterWaveView2类继承View public class Water

Android自定义控件实现水波纹效果

本文实例为大家分享了Android自定义控件实现水波纹的具体代码,供大家参考,具体内容如下 示例代码: MainActivity.java package com.example.mhy.shuibowen; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; public class MainActivity extends AppCompatActivity { @Override protec

Android实现简单水波纹效果

本文为大家分享了Android实现水波纹效果展示的具体代码,供大家参考,具体内容如下 一.效果 二.实现原理 自定义view,使用Path和贝塞尔曲线绘制,然后不断刷新,并且改变X.Y的值 主要知识点rQuadTo的使用 三.实现 WaveView.java public class WaveView extends View { private Paint mPaint; private final Path mPath; //波长 private int wavelength = 500;

js拖动滑块和点击水波纹效果实例代码

拖动滑块效果: 先看看效果图: <!doctype html> <html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <t

Android5.0中多种水波纹效果的实现代码

水波纹效果已经不是什么稀罕的东西了,用过5.0新控件的小伙伴都知道这个效果,可是如果使用一个TextView或者Button或者其它普通控件的话,你是否知道如何给它设置水波纹效果呢?OK,我们今天就来看看这个水波纹效果的实现.水波纹效果的实现有系统自带属性可以实现,我们也可以自定义实现效果. 1.系统自带水波纹实现方式 有界水波纹 水波纹效果大致上可以分为两种,一种是有界的,一种无界,我们先来看看有界水波纹效果: 效果: 代码: <TextView android:layout_width=&quo

C语言实现水波纹效果

本文实例为大家分享了C语言实现水波纹效果的具体代码,供大家参考,具体内容如下 #include <graphics.h> #include <conio.h> #include <stdio.h> #define PIC_HEIGHT 600 #define PIC_WIDTH 800 void FrameFun(); // 帧逻辑函数,处理每一帧的逻辑 void RenderFun(); // 帧渲染函数,输出每一帧到显示设备 IMAGE src_img; // 原位