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软件编程有所帮助。

(0)

相关推荐

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

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

  • 基于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实现点击Button产生水波纹效果

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

  • Android WaveView实现水流波动效果

    水流波动的波形都是三角波,曲线是正余弦曲线,但是Android中没有提供绘制正余弦曲线的API,好在Path类有个绘制贝塞尔曲线的方法quadTo,绘制出来的是2阶的贝塞尔曲线,要想实现波动效果,只能用它来绘制Path曲线.待会儿再讲解2阶的贝塞尔曲线是怎么回事,先来看实现的效果: 这个波长比较短,还看不到起伏,只是荡漾,把波长拉长再看一下: 已经可以看到起伏很明显了,再拉长看一下: 这个的起伏感就比较强了.利用这个波动效果,可以用在绘制水位线的时候使用到,还可以做一个波动的进度条WaveUpP

  • android贝塞尔曲线实现波浪效果

    本文实例为大家分享了android贝塞尔曲线实现波浪效果的具体代码,供大家参考,具体内容如下 因为手机录制gif不知道下什么软件好,所以暂时就先忽略效果图了 我在屏幕外多画了1.5个波浪,延伸至屏幕内.然后不断的循环,向右边移动.就有一种波浪的效果. 所以现在只需要画出左边的波长,然后再通过循环添加所有的波长即可. 第一个曲线已经确定了控制点和终点的坐标, 第二条曲线也可以很明显的看出来终点是在x轴的0点坐标,Y轴不变,而控制点是在负的波长的1/4的位置 有了上下曲线以后,其他的就可以直接通过循

  • Android简单自定义音乐波动特效图

    本文实例为大家分享了Android简单自定义音乐波动特效图的具体代码,供大家参考,具体内容如下 最终效果: 思路:就是绘制一个不断变化高度的矩形或者是宽虚线 1.自定义属性: <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="musicPlayViewAttr"> <!--指针颜色--> <a

  • Android实现渐变色水波纹效果

    本文实例为大家分享了Android实现渐变色水波纹效果的具体代码,供大家参考,具体内容如下 项目中使用到的效果,效果图如下: 代码实现: public class WaveView extends View { private Paint mPaint, mCriclePaint, mTextPaint; // 倾斜或旋转.快速变化,当在屏幕上画一条直线时, 横竖不会出现锯齿, // 但是当斜着画时, 就会出现锯齿的效果,所以需要设置抗锯齿 private DrawFilter mDrawFil

  • Android仿微信底部菜单栏效果

    前言 在市面上,大多数的APP都需要通过底部菜单栏来将程序的功能进行分类整理,通常都是分为3-5个大模块,从而正确有效地引导用户去使用我们的APP.实现底部菜单栏的方法也有很多种. 1.仿微信底部菜单栏(ViewPager+ImagerView+TextView) ......(其他方式后续会补充) 效果预览 首先来个开胃菜,看看实现效果: 先贴出项目所需的资源文件,这些可随个人自由更改颜色和文字 colors.xml <color name="bg_line_light_gray&quo

  • Android基于TextView属性android:ellipsize实现跑马灯效果的方法

    本文实例讲述了Android基于TextView属性android:ellipsize实现跑马灯效果的方法.分享给大家供大家参考,具体如下: Android系统中TextView实现跑马灯效果,必须具备以下几个条件: 1.android:ellipsize="marquee" 2.TextView必须单行显示,即内容必须超出TextView大小 3.TextView要获得焦点才能滚动 XML代码: android:ellipsize="marquee", andro

  • Android实现自定义的弹幕效果

    一.效果图 先来看看效果图吧~~ 二.实现原理方案 1.自定义ViewGroup-XCDanmuView,继承RelativeLayout来实现,当然也可以继承其他三大布局类哈 2.初始化若干个TextView(弹幕的item View,这里以TextView 为例,当然也可以其他了~),然后通过addView添加到自定义View中 3.通过addView添加到XCDanmuView中,位置在坐标,为了实现 从屏幕外移动进来的效果 我们还需要修改添加进来TextView的位置,以从右向左移动方向

  • Android实现图片轮播效果的两种方法

    大家在使用APP的过程中,经常会看到上部banner图片轮播的效果,那么今天我们就一起来学习一下,android中图片轮询的几种实现方法: 第一种:使用动画的方法实现:(代码繁琐) 这种发放需要:两个动画效果,一个布局,一个主类来实现,不多说了,来看代码吧: public class IamgeTrActivity extends Activity { /** Called when the activity is first created. */ public ImageView image

  • Android 类似微信登录输入框效果

    微信的登录输入框效果如下 进入自动打开自动启动软键盘 点击下一个输入框,下划线颜色改变 怎么实现这样的效果呢,其实非常简单! 简单的布局我就不说了,直接上干货. 1.实现进入自动弹出软键盘,在根文件中的Activity中设置 windowSoftInputMode 属性为 stateVisible|adjustResize 例如 <activity android:name=".SetLoginPasswordActivity" android:windowSoftInputMo

  • Android动画之3D翻转效果实现函数分析

    Android中的翻转动画效果的实现,首先看一下运行效果如上图所示. Android中并没有提供直接做3D翻转的动画,所以关于3D翻转的动画效果需要我们自己实现,那么我们首先来分析一下Animation 和 Transformation. Animation动画的主要接口,其中主要定义了动画的一些属性比如开始时间,持续时间,是否重复播放等等.而Transformation中则包含一个矩阵和alpha值,矩阵是用来做平移,旋转和缩放动画的,而alpha值是用来做alpha动画的,要实现3D旋转动画

随机推荐