http://blog.csdn.net/tianjian4592/article/details/44222565

在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了个让人兴奋的效果,兴致高昂的来找你,看了之后目的很明确,当然就是希望你能给她;

在这样的关键时候,身子板就一定得硬了,可千万别说不行,爷们儿怎么能说不行呢;

好了,为了让大家都能给妹纸们想要的,后面会逐渐分享一些比较比较不错的效果,目的只有一个,通过自定义view实现我们所能实现的动效;

今天主要分享水波纹效果:

1.标准正余弦水波纹;

2.非标准圆形液柱水波纹;

虽说都是水波纹,但两者在实现上差异是比较大的,一个通过正余弦函数模拟水波纹效果,另外一个会运用到图像的混合模式(PorterDuffXfermode);

先看效果:

                       

自定义View根据实际情况可以选择继承自View、TextView、ImageView或其他,我们先只需要了解如何利用android给我们提供好的利刃去满足UI妹纸;

这次的实现我们都选择继承view,在实现的过程中我们需要关注如下几个方法:

1.onMeasure():最先回调,用于控件的测量;

2.onSizeChanged():在onMeasure后面回调,可以拿到view的宽高等数据,在横竖屏切换时也会回调;

3.onDraw():真正的绘制部分,绘制的代码都写到这里面;

既然如此,我们先复写这三个方法,然后来实现如上两个效果;

一:标准正余弦水波纹

这种水波纹可以用具体函数模拟出具体的轨迹,所以思路基本如下:

1.确定水波函数方程

2.根据函数方程得出每一个波纹上点的坐标;

3.将水波进行平移,即将水波上的点不断的移动;

4.不断的重新绘制,生成动态水波纹;

有了上面的思路,我们一步一步进行实现:

正余弦函数方程为:

y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;

根据上面的方程选取自己觉得中意的波纹效果,确定对应参数的取值;

然后根据确定好的方程得出所有的方程上y的数值,并将所有y值保存在数组里:

  1. // 将周期定为view总宽度
  2. mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
  3. // 根据view总宽度得出所有对应的y值
  4. for (int i = 0; i < mTotalWidth; i++) {
  5. mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
  6. }

根据得出的所有y值,则可以在onDraw中通过如下代码绘制两条静态波纹:

  1. for (int i = 0; i < mTotalWidth; i++) {
  2. // 减400只是为了控制波纹绘制的y的在屏幕的位置,大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果
  3. // 绘制第一条水波纹
  4. canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
  5. mTotalHeight,
  6. mWavePaint);
  7. // 绘制第二条水波纹
  8. canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
  9. mTotalHeight,
  10. mWavePaint);
  11. }

这种方式类似于数学里面的细分法,一条波纹,如果横向以一个像素点为单位进行细分,则形成view总宽度条直线,并且每条直线的起点和终点我们都能知道,在此基础上我们只需要循环绘制出所有细分出来的直线(直线都是纵向的),则形成了一条静态的水波纹;

接下来我们让水波纹动起来,之前用了一个数组保存了所有的y值点,有两条水波纹,再利用两个同样大小的数组来保存两条波纹的y值数据,并不断的去改变这两个数组中的数据:

  1. private void resetPositonY() {
  2. // mXOneOffset代表当前第一条水波纹要移动的距离
  3. int yOneInterval = mYPositions.length - mXOneOffset;
  4. // 使用System.arraycopy方式重新填充第一条波纹的数据
  5. System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
  6. System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);
  7. int yTwoInterval = mYPositions.length - mXTwoOffset;
  8. System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
  9. yTwoInterval);
  10. System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
  11. }

如此下来只要不断的改变这两个数组的数据,然后不断刷新,即可生成动态水波纹了;

刷新可以调用invalidate()或postInvalidate(),区别在于后者可以在子线程中更新UI

整体代码如下:

  1. public class DynamicWave extends View {
  2. // 波纹颜色
  3. private static final int WAVE_PAINT_COLOR = 0x880000aa;
  4. // y = Asin(wx+b)+h
  5. private static final float STRETCH_FACTOR_A = 20;
  6. private static final int OFFSET_Y = 0;
  7. // 第一条水波移动速度
  8. private static final int TRANSLATE_X_SPEED_ONE = 7;
  9. // 第二条水波移动速度
  10. private static final int TRANSLATE_X_SPEED_TWO = 5;
  11. private float mCycleFactorW;
  12. private int mTotalWidth, mTotalHeight;
  13. private float[] mYPositions;
  14. private float[] mResetOneYPositions;
  15. private float[] mResetTwoYPositions;
  16. private int mXOffsetSpeedOne;
  17. private int mXOffsetSpeedTwo;
  18. private int mXOneOffset;
  19. private int mXTwoOffset;
  20. private Paint mWavePaint;
  21. private DrawFilter mDrawFilter;
  22. public DynamicWave(Context context, AttributeSet attrs) {
  23. super(context, attrs);
  24. // 将dp转化为px,用于控制不同分辨率上移动速度基本一致
  25. mXOffsetSpeedOne = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_ONE);
  26. mXOffsetSpeedTwo = UIUtils.dipToPx(context, TRANSLATE_X_SPEED_TWO);
  27. // 初始绘制波纹的画笔
  28. mWavePaint = new Paint();
  29. // 去除画笔锯齿
  30. mWavePaint.setAntiAlias(true);
  31. // 设置风格为实线
  32. mWavePaint.setStyle(Style.FILL);
  33. // 设置画笔颜色
  34. mWavePaint.setColor(WAVE_PAINT_COLOR);
  35. mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
  36. }
  37. @Override
  38. protected void onDraw(Canvas canvas) {
  39. super.onDraw(canvas);
  40. // 从canvas层面去除绘制时锯齿
  41. canvas.setDrawFilter(mDrawFilter);
  42. resetPositonY();
  43. for (int i = 0; i < mTotalWidth; i++) {
  44. // 减400只是为了控制波纹绘制的y的在屏幕的位置,大家可以改成一个变量,然后动态改变这个变量,从而形成波纹上升下降效果
  45. // 绘制第一条水波纹
  46. canvas.drawLine(i, mTotalHeight - mResetOneYPositions[i] - 400, i,
  47. mTotalHeight,
  48. mWavePaint);
  49. // 绘制第二条水波纹
  50. canvas.drawLine(i, mTotalHeight - mResetTwoYPositions[i] - 400, i,
  51. mTotalHeight,
  52. mWavePaint);
  53. }
  54. // 改变两条波纹的移动点
  55. mXOneOffset += mXOffsetSpeedOne;
  56. mXTwoOffset += mXOffsetSpeedTwo;
  57. // 如果已经移动到结尾处,则重头记录
  58. if (mXOneOffset >= mTotalWidth) {
  59. mXOneOffset = 0;
  60. }
  61. if (mXTwoOffset > mTotalWidth) {
  62. mXTwoOffset = 0;
  63. }
  64. // 引发view重绘,一般可以考虑延迟20-30ms重绘,空出时间片
  65. postInvalidate();
  66. }
  67. private void resetPositonY() {
  68. // mXOneOffset代表当前第一条水波纹要移动的距离
  69. int yOneInterval = mYPositions.length - mXOneOffset;
  70. // 使用System.arraycopy方式重新填充第一条波纹的数据
  71. System.arraycopy(mYPositions, mXOneOffset, mResetOneYPositions, 0, yOneInterval);
  72. System.arraycopy(mYPositions, 0, mResetOneYPositions, yOneInterval, mXOneOffset);
  73. int yTwoInterval = mYPositions.length - mXTwoOffset;
  74. System.arraycopy(mYPositions, mXTwoOffset, mResetTwoYPositions, 0,
  75. yTwoInterval);
  76. System.arraycopy(mYPositions, 0, mResetTwoYPositions, yTwoInterval, mXTwoOffset);
  77. }
  78. @Override
  79. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  80. super.onSizeChanged(w, h, oldw, oldh);
  81. // 记录下view的宽高
  82. mTotalWidth = w;
  83. mTotalHeight = h;
  84. // 用于保存原始波纹的y值
  85. mYPositions = new float[mTotalWidth];
  86. // 用于保存波纹一的y值
  87. mResetOneYPositions = new float[mTotalWidth];
  88. // 用于保存波纹二的y值
  89. mResetTwoYPositions = new float[mTotalWidth];
  90. // 将周期定为view总宽度
  91. mCycleFactorW = (float) (2 * Math.PI / mTotalWidth);
  92. // 根据view总宽度得出所有对应的y值
  93. for (int i = 0; i < mTotalWidth; i++) {
  94. mYPositions[i] = (float) (STRETCH_FACTOR_A * Math.sin(mCycleFactorW * i) + OFFSET_Y);
  95. }
  96. }

二:非标准圆形液柱水波纹

前面的波形使用函数模拟,这个我们换种方式,采用图进行实现,先用PS整张不像波纹的波纹图;

为了衔接紧密,首尾都比较平,并高度一致;

思路:

1.使用一个圆形图作为遮罩过滤波形图;

2.平移波纹图,即不断改变绘制的波纹图的区域,即srcRect;

3.当一个周期绘制完,则从波纹图的最前面重新计算;

首先初始化bitmap:

  1. private void initBitmap() {
  2. mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))
  3. .getBitmap();
  4. mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(
  5. R.drawable.circle_500))
  6. .getBitmap();
  7. }

使用drawable获取的方式,全局只会生成一份,并且系统会进行管理,而BitmapFactory.decode()出来的则decode多少次生成多少张,务必自己进行recycle;

然后绘制波浪和遮罩图,绘制时设置对应的混合模式:

  1. /*
  2. * 将绘制操作保存到新的图层
  3. */
  4. int sc = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, null, Canvas.ALL_SAVE_FLAG);
  5. // 设定要绘制的波纹部分
  6. mSrcRect.set(mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight);
  7. // 绘制波纹部分
  8. canvas.drawBitmap(mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint);
  9. // 设置图像的混合模式
  10. mBitmapPaint.setXfermode(mPorterDuffXfermode);
  11. // 绘制遮罩圆
  12. canvas.drawBitmap(mMaskBitmap, mMaskSrcRect, mMaskDestRect,
  13. mBitmapPaint);
  14. mBitmapPaint.setXfermode(null);
  15. canvas.restoreToCount(sc);

为了形成动态的波浪效果,单开一个线程动态更新要绘制的波浪的位置:

  1. new Thread() {
  2. public void run() {
  3. while (true) {
  4. // 不断改变绘制的波浪的位置
  5. mCurrentPosition += mSpeed;
  6. if (mCurrentPosition >= mSrcBitmap.getWidth()) {
  7. mCurrentPosition = 0;
  8. }
  9. try {
  10. // 为了保证效果的同时,尽可能将cpu空出来,供其他部分使用
  11. Thread.sleep(30);
  12. } catch (InterruptedException e) {
  13. }
  14. postInvalidate();
  15. }
  16. };
  17. }.start();

主要过程就以上这些,全部代码如下:

  1. public class PorterDuffXfermodeView extends View {
  2. private static final int WAVE_TRANS_SPEED = 4;
  3. private Paint mBitmapPaint, mPicPaint;
  4. private int mTotalWidth, mTotalHeight;
  5. private int mCenterX, mCenterY;
  6. private int mSpeed;
  7. private Bitmap mSrcBitmap;
  8. private Rect mSrcRect, mDestRect;
  9. private PorterDuffXfermode mPorterDuffXfermode;
  10. private Bitmap mMaskBitmap;
  11. private Rect mMaskSrcRect, mMaskDestRect;
  12. private PaintFlagsDrawFilter mDrawFilter;
  13. private int mCurrentPosition;
  14. public PorterDuffXfermodeView(Context context, AttributeSet attrs) {
  15. super(context, attrs);
  16. initPaint();
  17. initBitmap();
  18. mPorterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
  19. mSpeed = UIUtils.dipToPx(mContext, WAVE_TRANS_SPEED);
  20. mDrawFilter = new PaintFlagsDrawFilter(Paint.ANTI_ALIAS_FLAG, Paint.DITHER_FLAG);
  21. new Thread() {
  22. public void run() {
  23. while (true) {
  24. // 不断改变绘制的波浪的位置
  25. mCurrentPosition += mSpeed;
  26. if (mCurrentPosition >= mSrcBitmap.getWidth()) {
  27. mCurrentPosition = 0;
  28. }
  29. try {
  30. // 为了保证效果的同时,尽可能将cpu空出来,供其他部分使用
  31. Thread.sleep(30);
  32. } catch (InterruptedException e) {
  33. }
  34. postInvalidate();
  35. }
  36. };
  37. }.start();
  38. }
  39. @Override
  40. protected void onDraw(Canvas canvas) {
  41. super.onDraw(canvas);
  42. // 从canvas层面去除锯齿
  43. canvas.setDrawFilter(mDrawFilter);
  44. canvas.drawColor(Color.TRANSPARENT);
  45. /*
  46. * 将绘制操作保存到新的图层
  47. */
  48. int sc = canvas.saveLayer(0, 0, mTotalWidth, mTotalHeight, null, Canvas.ALL_SAVE_FLAG);
  49. // 设定要绘制的波纹部分
  50. mSrcRect.set(mCurrentPosition, 0, mCurrentPosition + mCenterX, mTotalHeight);
  51. // 绘制波纹部分
  52. canvas.drawBitmap(mSrcBitmap, mSrcRect, mDestRect, mBitmapPaint);
  53. // 设置图像的混合模式
  54. mBitmapPaint.setXfermode(mPorterDuffXfermode);
  55. // 绘制遮罩圆
  56. canvas.drawBitmap(mMaskBitmap, mMaskSrcRect, mMaskDestRect,
  57. mBitmapPaint);
  58. mBitmapPaint.setXfermode(null);
  59. canvas.restoreToCount(sc);
  60. }
  61. // 初始化bitmap
  62. private void initBitmap() {
  63. mSrcBitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.wave_2000))
  64. .getBitmap();
  65. mMaskBitmap = ((BitmapDrawable) getResources().getDrawable(
  66. R.drawable.circle_500))
  67. .getBitmap();
  68. }
  69. // 初始化画笔paint
  70. private void initPaint() {
  71. mBitmapPaint = new Paint();
  72. // 防抖动
  73. mBitmapPaint.setDither(true);
  74. // 开启图像过滤
  75. mBitmapPaint.setFilterBitmap(true);
  76. mPicPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  77. mPicPaint.setDither(true);
  78. mPicPaint.setColor(Color.RED);
  79. }
  80. @Override
  81. protected void onSizeChanged(int w, int h, int oldw, int oldh) {
  82. super.onSizeChanged(w, h, oldw, oldh);
  83. mTotalWidth = w;
  84. mTotalHeight = h;
  85. mCenterX = mTotalWidth / 2;
  86. mCenterY = mTotalHeight / 2;
  87. mSrcRect = new Rect();
  88. mDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);
  89. int maskWidth = mMaskBitmap.getWidth();
  90. int maskHeight = mMaskBitmap.getHeight();
  91. mMaskSrcRect = new Rect(0, 0, maskWidth, maskHeight);
  92. mMaskDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);
  93. }
  94. }

源码CSDN下载地址:http://download.csdn.net/download/tianjian4592/8496385

Android 自定义view实现水波纹效果的更多相关文章

  1. 自定义view实现水波纹效果

    水波纹效果: 1.标准正余弦水波纹: 2.非标准圆形液柱水波纹: 虽说都是水波纹,但两者在实现上差异是比较大的,一个通过正余弦函数模拟水波纹效果,另外一个会运用到图像的混合模式(PorterDuffX ...

  2. Android点击Button水波纹效果

    先上图,看看接下来我要向大家介绍的是个什么东西,例如以下图: 接下来要介绍的就是怎样实现上述图中的波纹效果.这样的效果假设大家没有体验过的话,能够看看百度手机卫士或者360手机卫士,里面的按钮点击效果 ...

  3. android自定义view实现progressbar的效果

    一键清理是很多Launcher都会带有的功能,其效果也比较美观.实现方式也许有很多中,其中常见的是使用图片drawable来完成的,具体可以参考这篇文章:模仿实现360桌面水晶球式的一键清理特效.本文 ...

  4. android自定义View之3D索引效果

    效果图: 我的小霸王太卡了. 最近工作比较忙,今天搞了一下午才搞出来这个效果,这种效果有很多种实现方式,最常见的应该是用贝塞尔曲线实现的.今天我们来看另一种不同的实现方式,只需要用到 canvas.s ...

  5. Android自定义View——刮刮卡效果

    想要红包的实现效果的可以关注我的博客,仿饿了么红包 下层图片:我们的红包的图片 上层图片:有两部分 一部分是灰色背景 一部分是拥有透明度为0,并且模式为交集的画笔 使用滑动监听,滑动时,用透明度为0的 ...

  6. android自定义控件(4)-自定义水波纹效果

    一.实现单击出现水波纹单圈效果: 照例来说,还是一个自定义控件,观察这个效果,发现应该需要重写onTouchEvent和onDraw方法,通过在onTouchEvent中获取触摸的坐标,然后以这个坐标 ...

  7. Android自定义控件-Path之贝赛尔曲线和手势轨迹、水波纹效果

    从这篇开始,我将延续androidGraphics系列文章把图片相关的知识给大家讲完,这一篇先稍微进阶一下,给大家把<android Graphics(二):路径及文字>略去的quadTo ...

  8. Android 颜色渲染(七) RadialGradient 环形渲染实现水波纹效果

    利用环形渲染我们可以做到什么? 其实很多都是非常常见的,比如上一篇实现的帮帮糖效果, 彩色的热气球,比如这里要讲到的水波纹效果,或者也可以理解为扩散色渲染效果 首先看一下效果图: 轻触屏幕,即可看到对 ...

  9. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

随机推荐

  1. IOS 表视图(UITableVIew)的使用方法(2)名单的分段显示

    我们可以采用名字分段法,名字分段会在之后的小节中显示,这是转而使用球员的角色分段发,以最直接的入手点讲解好UITableView的分段使用方法.本节示例时基于上节的SimpleTableViewCon ...

  2. 解决用户 'IIS APPPOOL\Classic .NET AppPool' 登录失败

    解决用户 'IIS APPPOOL\Classic .NET AppPool' 登录失败 windows 7进入iis管理器 本地应用程序池 选中classic. net appPool 选择右侧的 ...

  3. 关于document.selection和TextRange对象的介绍

    document.selection只有IE支持 window.getSelection()也只有FireFox和Safari支持,都不是标准语法. selection 对象代表了当前激活选中区,即高 ...

  4. Qt的“undefined reference to `vtable for”错误解决(手动解决,加深理解)

    使用QT编程时,当用户自定义了一个类,只要类中使用了信号或槽. Code::Blocks编译就会报错(undefined reference to `vtable for). Google上有很多这个 ...

  5. Qt多线程编程总结(一)(所有GUI对象都是线程不安全的)

    Qt对线程提供了支持,基本形式有独立于平台的线程类.线程安全方式的事件传递和一个全局Qt库互斥量允许你可以从不同的线程调用Qt方法. 这个文档是提供给那些对多线程编程有丰富的知识和经验的听众的.推荐阅 ...

  6. QT 子窗口监听主窗口信号(超级简单,但是好用,比如主窗口移动的时候,子窗口不要再继续处理任务)

    MainWindow *ptr = NULL; ptr = (MainWindow*)parentWidget(); connect(ptr, SIGNAL(param_result(bool)), ...

  7. Failure [INSTALL_FAILED_OLDER_SDK]

    在AndroidManifest.xml 中把  <uses-sdk android:minSdkVersion="21" />的版本调节的低一点

  8. python OptionParser模块

    Python中强大的选项处理模块. #!/usr/bin/python from optparse import OptionParser parser = OptionParser() parser ...

  9. VS2010/MFC:模态对话框及其弹出过程

    模态对话框及其弹出过程 加法计算器对话框程序大家照着做一遍后,相信对基于对话框的程序有些了解了,有个好的开始对于以后的学习大有裨益.趁热打铁,这一节讲讲什么是模态对话框和非模态对话框,以及模态对话框怎 ...

  10. 使用mongodb存取lbs数据

    1,在mongodb中创建lbs_db数据库,collection名称lbs_info,要使用lbs查询功能,需要对二维数据列建立索引 db.lbs_info.ensureIndex( { locs ...