Android自定义控件实战——水流波动效果的实现WaveView
转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38556891
水流波动的波形都是三角波,曲线是正余弦曲线,但是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自定义控件实战——水流波动效果的实现WaveView的更多相关文章
- Android自定义控件简单实现ratingbar效果
先上图: 一开始让我自定义控件我是拒绝的,因为android很早以前就有一个控件ratingbar,但是设置样式的时候我发现把图片设置小一点就显示不全,一直找不到解办法!(可以设置系统的自带的小样式) ...
- android自定义控件(5)-实现ViewPager效果
对于系统的ViewGroup我们已经是十分熟悉了,最常用的LinearLayout和RelativeLayout几乎是天天要打交道,下面我们就来看看,如何一步一步将其实现: 一.首先当然也是最通常的新 ...
- Android自定义控件实战——滚动选择器PickerView
转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38513301 手机里设置闹钟需要选择时间,那个选择时间的控件就是滚动选择器, ...
- Android自定义控件实战——仿淘宝商品浏览界面
转载请声明出处http://blog.csdn.net/zhongkejingwang/article/details/38656929 用手机淘宝浏览商品详情时,商品图片是放在后面的,在第一个Scr ...
- Android自定义控件练手——波浪效果
这一次要绘制出波浪效果,也是小白的我第一次还望轻喷.首先当然是展示效果图啦: 一.首先来说说实现思路. 想到波浪效果,当然我第一反应是用正余弦波来设计啦(也能通过贝塞尔曲线,这里我不提及这个方法但是在 ...
- Android SurfaceView实战 打造抽奖转盘
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41722441 ,本文出自:[张鸿洋的博客] 1.概述 今天给大家带来Surfac ...
- android自定义控件(4)-自定义水波纹效果
一.实现单击出现水波纹单圈效果: 照例来说,还是一个自定义控件,观察这个效果,发现应该需要重写onTouchEvent和onDraw方法,通过在onTouchEvent中获取触摸的坐标,然后以这个坐标 ...
- Android 自定义控件实现刮刮卡效果 真的就只是刮刮卡么
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40162163 , 本文出自:[张鸿洋的博客] 很久以前也过一个html5的刮刮卡 ...
- (转载)Android项目实战(二十八):使用Zxing实现二维码及优化实例
Android项目实战(二十八):使用Zxing实现二维码及优化实例 作者:听着music睡 字体:[增加 减小] 类型:转载 时间:2016-11-21我要评论 这篇文章主要介绍了Android项目 ...
随机推荐
- iOS网络请求基础
这篇是关于网络请求的,结合公司的实际情况编写,如果有不同意见欢迎留言共同讨论. iOS在9.0之后彻底放弃了NSURLConnection,现在已经改用了NSURLSession进行网络请求.一般现在 ...
- 使用Highcharts生成折线图与曲线图
折线图与曲线图可以显示随时间而变化的连续数据,因此非常适用于显示在相等时间间隔下数据的趋势.本文将结合Highcharts,生成一个城市气温变化折线图和一个随时间动态即时显示CPU走势的曲线图. 如果 ...
- UVa202 Repeating Decimals
#include <stdio.h>#include <map>using namespace std; int main(){ int a, b, c, q, r, p ...
- Web学习之自定义标签
1.编写一个实现Tag接口的Java类(标签处理器类) package me.gacl.web.tag; import java.io.IOException; import javax.servle ...
- 梳理下phpmyadmin改root密码后登录不上的问题。
一, 登陆phpmyadmin,然后点击左侧进入mysql数据库,在顶部点击“mysql”进入sql输入界面.输入以下命令: update user set password=password('12 ...
- QT实现,通过URL下载文件的接口实现
今天来把坑填上. 具体就是提供一个URL,并通过这个URL下载文件. MyDownloader.h: #ifndef MYDOWNLOADER_H #define MYDOWNLOADER_H cla ...
- A Brief Introduction to Multiset[STL]
基础 multiset是<set>库中一个非常有用的类型,它可以看成一个序列,插入一个数,删除一个数都能够在O(logn)的时间内完成,而且他能时刻保证序列中的数是有序的,而且序列中可以存 ...
- JavaEE Tutorials (9) - 运行持久化示例
9.1order应用118 9.1.1order应用中的实体关系119 9.1.2order应用中的主键121 9.1.3实体映射到多个数据库表125 9.1.4order应用中的层叠操作125 9. ...
- HDU 2227 Find the nondecreasing subsequences
题目大意:给定一个序列,求出其所有的上升子序列. 题解:一开始我以为是动态规划,后来发现离散后树状数组很好做,首先,c保存的是第i位上升子系列有几个,那么树状数组的sum就直接是现在的答案了,不过更新 ...
- C# windows ce编程-----我的第一次
最近公司要求开发抄表软件,软件分为PC端和手持终端(简称HHU),HHU是基于英文版的windows ce6.0操作系统,开发环境要求VS2005+SQLite数据库,开发语言为C#,因为是第一次基本 ...