Android WaveView实现水流波动效果_Android

   水流波动的波形都是三角波,曲线是正余弦曲线,但是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软件编程有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索android
, WaveView
水流波动
html水流波动、android 水流波动、waveview、android waveview、waveview 使用方法,以便于您获取更多的相关知识。

时间: 2024-07-30 10:33:48

Android WaveView实现水流波动效果_Android的相关文章

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

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

Android实现字母雨的效果_Android

首先来看效果: 一.实现原理 在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果: 字母对象-->字母线条对象-->界面效果 每个字母都应该知道自己的位置坐标,自己上面的字母.以及自己的透明度: class HackCode{ Point p = new Point();//每一个字母的坐标 int alpha = 255;//透明度值 默认255 String code = &q

Android自定义View实现打字机效果_Android

一.先来看看效果演示 二.实现原理: 这个其实不难实现,通过一个定时器不断调用TextView的setText就行了,在setText的时候播放打字的音效. 具体代码如下: import java.util.Timer; import java.util.TimerTask; import android.content.Context; import android.media.MediaPlayer; import android.text.TextUtils; import android

Android实现Flip翻转动画效果_Android

本文实例讲述了Android实现Flip翻转动画效果的方法,分享给大家供大家学习借鉴. 具体实现代码如下: LinearLayout locationLL = (LinearLayout) findViewById(R.id.locationLL); LinearLayout baseLL = (LinearLayout) findViewById(R.id.baseLL); private void flipit() { Interpolator accelerator = new Accel

简单实用的Android UI微博动态点赞效果_Android

说起空间动态.微博的点赞效果,网上也是很泛滥,各种实现与效果一大堆.而详细实现的部分,讲述的也是参差不齐,另一方面估计也有很多大侠也不屑一顾,觉得完全没必要单独开篇来写和讲解吧.毕竟,也就是两个view和一些简单的动画效果罢了. 单若是只讲这些,我自然也是不愿花这番功夫的.虽然自己很菜,可也不甘于太菜.所以偶尔看到些好东西,可以延伸学写下,我还是很情愿拿出来用用,顺带秀一秀逼格什么的. 不扯太多,先说说今天实现点赞效果用到的自以为不错的两个点: Checkable 用来扩展View实现选中状态切

Android项目实现黑名单拦截效果_Android

本文实例讲述了Android编程中黑名单的实现方法.分享给大家供大家参考,具体如下: 1,黑名单数据库创建 三个字段(_id 自增长字段 phone 黑名单号码 mode 拦截类型) 创建表的sql语句 create table blacknumber (_id integer primary key autoincrement , phone varchar(20), mode varchar(5)); 结合项目,去创建数据库,以及相应的表 2.BlackNumberDao BlackNumb

Android仿人人网滑动侧边栏效果_Android

很多应用为了节省空间而又使界面能够充足的显示信息,大多数应用都采用了侧边栏的方式,如下图:        来说说它的思路,底下是两个或多个视图,分别通过控制它们的宽度.左边距来控制它们的显示,来看看代码  activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/too

Android实现创意LoadingView动画效果_Android

Android上的热火锅煮萝卜蔬菜的Loading动画效果. 这是一个锅煮萝卜的Loading动画,效果仿照自之前IOS上看到的一个效果,觉得挺有意思,就移植过来了,在此完成了Dialog的样式,方便使用者作为LoadingView去使用. 关键性代码: package yellow5a5.demo.boilingloadingview.View; import android.animation.Animator; import android.animation.AnimatorListen

Android新闻广告条滚动效果_Android

项目中需要用到类似公告栏的控件,能用的基本不支持多行显示,于是只好自己动手,苦于没有自定义过一个像样的控件,借鉴Android公告条demo,实现了多行向上滚动的控件.在原控件基础之上添加如下功能:  •传入数据分页显示  •添加Left Drawable  •手指触摸事件处理  •添加3D动画翻滚效果 效果图 源码 package com.android.view; import android.content.Context; import android.content.res.Typed