发现淘宝个人页顶部的自定义控件很炫酷啊有没有(IOS端),它这里是一个动态的双波纹效果,由于IOS端的效果它有周期性地渐变振幅的功能,比较复杂。对于振幅的渐变效果,当时就想着是怎么实现的,冥思苦想了老半天不得果(每次都重新计算设置正弦函数值,有点太耗费性能了)。
  后面又拿起安卓机看了一下安卓客户端的效果,结果发现是个静态的双波纹,What ? 和IOS的差距咋那么大? 想了想,淘宝应该是出于对于安卓机器性能参差不齐的考虑,所以他们的研发人员就没有在安卓机实现动态效果。好了,发一下截图对比:

Android端的效果:

 IOS端的效果:

  对于一个有追求的攻城狮来说,一知半解是最不能忍受的,所以说干就干,咱也弄一个出来。秉着IT界的真理:“不要重复造轮子”的思想,先在网上查资料看有没有人造好了这个轮子。果然,有类似的博客,但是很多人评论说性能消耗很大,实际使用会比较卡,尤其是在低端机上面。类似效果的博客地址:http://blog.csdn.net/tianjian4592/article/details/44222565。鉴于这样的情况,就自己进行优化吧~按照惯例,先发一下我最后实现出来的效果:
  
  
  
如果你只是抱着直接拿来用的心态的话,这个自定义控件我上传到JCenter上面去了,可以直接

1.引入依赖项目:

compile 'com.xiaosong520:doublewaveview:1.0.1' 

2.然后在布局文件中:

 <com.doublewave.DoubleWaveViewandroid:id="@+id/waveView"android:layout_width="match_parent"android:layout_height="wrap_content"DoubleWaveView:speedOne="8"DoubleWaveView:speedTwo="6"DoubleWaveView:peakValue="20dp"DoubleWaveView:waveHeight="200dp"DoubleWaveView:waveColor="@color/colorBlue"/>

由于是自定义控件,需要在根布局中添加适配:
xmlns:DoubleWaveView="http://schemas.android.com/apk/res-auto"

3.在Activity中:

 waveView = (DoubleWaveView) findViewById(R.id.waveView);waveView.setAnim(false);//默认是开启动画效果的,选false可关闭waveView.setAnim(true);//如果已经关闭,重新设置True开启动画

简单的三步,就可以使用了。

如果你是想理解透它的实现原理,那么请继续往下看~

在开始码码码之前,得做好准备工作,俗话说得好,磨刀不误砍柴工嘛,先搞清楚实现思路:

 1.确定水波纹的正弦函数方程;

 2.根据函数方程得出每一个波纹上点的坐标(单位:px),并保存到一维数组中;

 3.根据正弦函数的坐标不断绘制竖直直线,形成一个静态波纹图;

 4.不断调用onDraw方法,改变正弦函数Y值进行重绘,生成动态水波纹。

  
  Step 1:生成波纹曲线
  
  波纹曲线是利用正弦函数来实现的,正弦函数的方程式:y = A*sin(ωx+b)+h
一开始自己也懵逼了,这几个参数分别都是什么来着了啊?努力回忆中。。。 当年的理科学霸小正太现在已然成了老年痴呆社会青年,感觉自己应该是读了个假高中。关于正弦函数的定义,如果你也忘记了的话,自行Google 百度温习一下知识吧~查找资料后可以确定:w影响周期,A影响振幅,h影响y轴位置,b为初相;周期T = 2π/ω。

   Step 2:自定义DoubleWaveView的属性

  在步骤一中,我们已经了解了正弦函数的各个参数的含义以及计算方法。那么接下来我们就开始自定义View:
  首先创建一个DoubleWaveView类 ,继承自View。 由于波纹的颜色、振幅、移动速度等,可能会根据实际情况需要变动,所以我们接下来在项目的res-values目录下创建一个attrs的xml 文件,用于创建自定义属性。关于自定义View的步骤如果还不是蛮太懂的话,可以补习一下自定义View 的知识,推荐张鸿洋的这篇博客: Android 自定义View (一)
  
这里我定义的attrs属性如下:

<?xml version="1.0" encoding="utf-8"?>
<resources><attr name="peakValue" format="dimension"/><attr name="waveColor" format="color" /><attr name="speedOne" format="integer" /><attr name="speedTwo" format="integer" /><attr name="waveHeight" format="dimension" /><declare-styleable name="DoubleWaveView"><attr name="peakValue" /><attr name="waveColor" /><attr name="speedOne" /><attr name="speedTwo" /><attr name="waveHeight" /></declare-styleable></resources>

正弦函数方程式: y = A*sin(wx+b)+h
五个属性代表的含义:

peakValue :振幅 ,即对应的是A
waveHeight:波浪距离控件底部的高度。描述起来可能有点歧义,看下面的图就明白了

speedOne:第一条波浪的移动速度
speedTwo:第二条波浪的移动速度
waveColor:水波的颜色

Step 3: 绘制双波纹图形

  1.先把之前在attr.xml中定义好的属性值,通过构造函数获取到,并设置好画笔,代码都有注释,就不多说了,如果有疑问可以回复讨论:

    public DoubleWaveView(Context context, AttributeSet attrs){this(context, attrs, 0);}public DoubleWaveView(Context context){this(context, null);}public DoubleWaveView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);//获取自定义属性值TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.DoubleWaveView, defStyle, 0);int n = a.getIndexCount();for (int i = 0; i < n; i++) {int attr = a.getIndex(i);switch (attr) {case R.styleable.DoubleWaveView_peakValue://振幅默认是30dpSTRETCH_FACTOR_A =  a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, getResources().getDisplayMetrics()));break;case R.styleable.DoubleWaveView_waveColor:WAVE_PAINT_COLOR = a.getColor(attr, 0x881E90FF);//默认是蓝色break;case R.styleable.DoubleWaveView_speedOne:TRANSLATE_X_SPEED_ONE = a.getInteger(attr,7);//默认是7break;case R.styleable.DoubleWaveView_speedTwo:TRANSLATE_X_SPEED_TWO = a.getInteger(attr,5);//默认是5break;case R.styleable.DoubleWaveView_waveHeight:WaveHeight = a.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 100, getResources().getDisplayMetrics()));//默认100dpbreak;}}a.recycle();// 将dp转化为px,用于控制不同分辨率上移动速度基本一致mXOffsetSpeedOne = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_ONE);mXOffsetSpeedTwo = DensityUtil.dip2px(context, TRANSLATE_X_SPEED_TWO);// 初始绘制波纹的画笔mWavePaint = new Paint();// 去除画笔锯齿mWavePaint.setAntiAlias(true);// 设置风格为实线mWavePaint.setStyle(Paint.Style.FILL);// 设置画笔颜色mWavePaint.setColor(WAVE_PAINT_COLOR);mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);}

其中需要用到的密度转换工具类:

/*** @TODO<分辨率转换工具类>* @author 小嵩* @date 2016-8-3 09:20:46*/
public class DensityUtil {/*** 根据手机的分辨率从 dp 的单位 转成为 px(像素)*/public static int dip2px(Context context, float dpValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale + 0.5f);}/*** 根据手机的分辨率从 px(像素) 的单位 转成为 dp*/public static int px2dip(Context context, float pxValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (pxValue / scale + 0.5f);}
}

2.覆盖onDraw方法和onSizeChanged方法

3.在onSizeChanged方法中,获取控件的宽高,根据宽高计算正弦波纹周期以及对应的Y值:

 @Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);// 记录下控件设置的宽高mTotalWidth = w;mTotalHeight = h;// 一维数组, 用于保存原始波纹的y值mYPositions = new float[mTotalWidth];// 将Sin函数周期定为view总宽度, ω = 2π/TmCycleFactorW = (float) (2 * Math.PI / mTotalWidth);// 根据view总宽度得出所有对应的y值,即计算出正弦图形对应位置for (int i = 0; i < mTotalWidth; i++) {mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);}}

4.在onDraw方法中绘制双波纹图形,绘制方法是:
public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint),几个参数代表的含义应该都能看明白吧,分别是起点X、Y值,终点X、Y值,画笔对象。

  其实就是在竖直方向一条一条直线地画,起点是从onSizeChanged中得到的正弦函数Y值的数组中得到的,而终点,就是控件底部的坐标。
画笔绘制过程如下图所示:

  所以控件宽度的像素有多少,一个波纹图形需要调用多少次drawLine方法。

Step 4: 不断重绘双波纹图形,形成动态效果

在绘制完成整个静态图形的过程后,通过 postInvalidate()方法来通知系统更新UI ,其实就是相当于重新调用了onDraw方法,从而进行平移,实现动态的效果。相关代码如下。

网上的那些例子,绘制方案大都是每次平移都拷贝4次数组,造成很大的内存开销:

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);...//中间省略...resetPositonY();//拷贝两个波浪正弦函数值的数组(每调用一次onDraw,都需要4次数组的拷贝)
for (int i = 0; i < mTotalWidth; i++) {// 减500只是为了控制波纹绘制的y的在屏幕的位置(高度),大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果// 绘制第一条水波纹(竖直方向)canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 500, i, mTotalHeight, mWavePaint);// 绘制第二条水波纹(竖直方向)canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 500, i, mTotalHeight, mWavePaint);}}
private void resetPositonY() {//Copy数组// mXOneOffset代表当前第一条水波纹要移动的距离int yOneInterval = mYPositions.length - mXOneOffset;// 使用System.arraycopy方式重新填充第一条波纹的数据System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);int yTwoInterval = mYPositions.length - mXTwoOffset;System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,yTwoInterval);System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);}

以上方法不推荐使用,太耗费内存了,实际使用的效果会很卡,所以这里我整理了一下,综合别人的处理方案做了优化:

 @Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);//从canvas层面去除绘制时的锯齿canvas.setDrawFilter(mDrawFilter);for(int i=0,j=0,k=0;i<mTotalWidth;i++){if(i+mXOneOffset<mTotalWidth){//第一条波纹图形绘制canvas.drawLine(i,mTotalHeight-mYPositions[mXOneOffset+i]-WaveHeight,i,mTotalHeight,mWavePaint);}else {//大于周期值,则设置为j(与相位相关,已移动的X距离,最大值为一个周期,即控件的宽度)canvas.drawLine(i,mTotalHeight-mYPositions[j]-WaveHeight,i,mTotalHeight,mWavePaint);j++;}if(i+mXTwoOffset<mTotalWidth){//第二条波纹图形绘制canvas.drawLine(i,mTotalHeight-mYPositions[mXTwoOffset+i]-WaveHeight,i,mTotalHeight,mWavePaint);}else {//大于周期值,则设置为k(与相位相关,已移动的X距离)canvas.drawLine(i,mTotalHeight-mYPositions[k]-WaveHeight,i,mTotalHeight,mWavePaint);k++;}}// 改变两条波纹的移动点mXOneOffset += mXOffsetSpeedOne;mXTwoOffset += mXOffsetSpeedTwo;// 如果已经移动到结尾处,则重头记录if (mXOneOffset >= mTotalWidth) {mXOneOffset = 0;}if (mXTwoOffset > mTotalWidth) {mXTwoOffset = 0;}// 引发view重绘,可以考虑延迟10-30ms重绘,空出时间绘制if (isAnim){new Thread(mRunnable).start();}}private Runnable mRunnable = new Runnable() {@Overridepublic void run() {{try {//界面更新的频率,每20ms更新一次界面Thread.sleep(20);//通知系统更新界面,相当于调用了onDraw函数postInvalidate();} catch (InterruptedException e) {e.printStackTrace();}}}};

另外我还完善了动画效果的启动和暂停设置,有兴趣可下载sample demo查看完整代码 。

GitHub 地址 :DoubleWave 双波纹自定义View

Android双波浪自定义控件(DoubleWaveView)相关推荐

  1. android自定义波浪图,Android自定义控件--波浪图控件

    今天给大家分享一个android的波浪图控件制作.具体效果如下图所示: 上次有个app使用了这个控件,感觉特别酷炫.今天讲解一下这个控件的思路分析与代码编写. 思路分析: 1.绘制波浪图 2.移动波浪 ...

  2. android 海浪动画,android自定义波浪加载动画的实现代码

    本文实例为大家分享了android自定义波浪加载动画的具体代码,供大家参考,具体内容如下 效果图 1.自定义控件 WaveView package com.example.wh.myapplicati ...

  3. linux系统 安卓系统安装教程,最简单的Ubuntu Touch Android 双系统安装方式

    Ubuntu Touch 和Android 双启动的官方工具来了,Canonical 发布了一个可以让Nexus 设备实现在Ubuntu Touch 和Android 之间进行双启动的工具.发放这个的 ...

  4. 基于容器原理(docker、lxc、cells)的Android 双系统设计概要

    写在前面 最近一两年预研加开发android双系统:中途用过不少开源代码或者研读过大牛BLOG,现开放双系统设计原理来回报社区. 备注:我是在android6.0上实现的. 这个项目的原型来自于,哥伦 ...

  5. Android开发技巧——自定义控件之自定义属性

    Android开发技巧--自定义控件之自定义属性 掌握自定义控件是很重要的,因为通过自定义控件,能够:解决UI问题,优化布局性能,简化布局代码. 上一篇讲了如何通过xml把几个控件组织起来,并继承某个 ...

  6. html5双波浪线怎么添加,在WPS中如何给段落添加双波浪线边框

    WPS给文章的段落添加一个很好的边框,使整个段落看起来大方有条理.推荐一种波浪边框,这种边框添加后效果感是非常不错的,看起来就像是我们平时生活中写的明信片一样.以下是学习啦小编为您带来的关于在WPS中 ...

  7. 安装windows和android双系统,安装Windows和Android双系统.doc

    安装Windows和Android双系统 轻松搞定Windows和Android双系统 前言:本文介绍在windows操作系统的基础上安装Android,并且正常引导双系统启动.采用先添加启动项,后安 ...

  8. java解析word 波浪线,word页面边框双波浪线

    word如何在文本最后添加边框线或者双波浪线~ 可以教你一个简单易懂的:1.在你文本外面画一个表格,word画表格:表格--绘制表格2.你可以在工具栏上找到边框颜色,线形,磅数等,如果你需要曲线就选择 ...

  9. 机甲Android on ios,全球首款iOS+Android双系统硬件机甲震撼上市

    原标题:全球首款iOS+Android双系统硬件机甲震撼上市 2016年7月20日,专为苹果打造的高端智能外设--机甲在北京震撼发布.这款全球首创的iOS/Android秒级切换双系统一举颠覆iOS和 ...

最新文章

  1. api接口怎么对接_系统对接项目管理方面怎么做?从一次项目接口对接说起
  2. python继承属性_python继承,属性查找顺序
  3. 《编写可维护的Javascript》学习总结
  4. win 2008 R2 域服务器策略同步异常解决方案。
  5. python 获取内存使用率_获取一个python实例的总内存和cpu使用率
  6. mongodb数据库命令操作(转)
  7. 基于小米球(Ngrok)实现内网穿透
  8. 【雷达】一维和二维自适应波束形成(DBF))DBF附matlab代码
  9. 【澳大利亚英语】我的英语笔记。。。
  10. golang中的reflect(反射)
  11. 20155201李卓雯 20155212江振思 20155313杨瀚《信息安全技术》 实验三 数字证书应用...
  12. 戴尔Inspiron 灵越 15 5000(5580)BIOS设置U盘启动
  13. Bit Twiddling Hacks
  14. 单片机C51复习题(课后习题及答案)
  15. Django项目实践(商城):十五、商品列表页面
  16. xp系统计算机蓝屏,xp电脑开机蓝屏代码0×0000000A怎么办
  17. 微信H5配置测试的appId和secret
  18. 抓取抖音数据(fiddler+uiAutomator2+java)
  19. React--key值详解
  20. 关于开源授权协议 GPL 和 LGPL

热门文章

  1. 基于数据分析的“用户群组+推送”,提升用户粘性
  2. ios 渐变透明背景_骚气渐变色的海报设计怎么做?
  3. CentOS Redis安装报错:“You need tcl 8.5 or newer in order to run the Redis test”问题解决
  4. 银联的bankall_Visa禁止银行利用中国银联(China UnionPay Co.)境外交易
  5. [***Model mj_objectArrayWithKeyValuesArray:]: unrecognized selector sent to class 0x10ace5df0
  6. 大数据周周看:百分点集团全资并购极速洞察,精准医疗创企“海普洛斯”获2.1亿元融资
  7. python中面向对象的缺点_python中的面向对象和面向过程
  8. 微库为8亿会员的微信“偷偷”干了什么?
  9. 陕汽汉德、一汽集团信息化调研报告
  10. Cocos Creator 新手引导制作