zoukankan      html  css  js  c++  java
  • 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:

     

    [java] view plaincopy
     
    1. package com.jingchen.waveview; 
    2.  
    3. import java.util.ArrayList; 
    4. import java.util.List; 
    5. import java.util.Timer; 
    6. import java.util.TimerTask; 
    7.  
    8. import android.content.Context; 
    9. import android.graphics.Canvas; 
    10. import android.graphics.Color; 
    11. import android.graphics.Paint; 
    12. import android.graphics.Paint.Align; 
    13. import android.graphics.Paint.Style; 
    14. import android.graphics.Region.Op; 
    15. import android.graphics.Path; 
    16. import android.graphics.RectF; 
    17. import android.os.Handler; 
    18. import android.os.Message; 
    19. import android.util.AttributeSet; 
    20. import android.view.View; 
    21.  
    22. /**
    23. * 水流波动控件
    24. *
    25. * @author chenjing
    26. *
    27. */ 
    28. public class WaveView extends View 
    29.  
    30.     private int mViewWidth; 
    31.     private int mViewHeight; 
    32.  
    33.     /**
    34.      * 水位线
    35.      */ 
    36.     private float mLevelLine; 
    37.  
    38.     /**
    39.      * 波浪起伏幅度
    40.      */ 
    41.     private float mWaveHeight = 80
    42.     /**
    43.      * 波长
    44.      */ 
    45.     private float mWaveWidth = 200
    46.     /**
    47.      * 被隐藏的最左边的波形
    48.      */ 
    49.     private float mLeftSide; 
    50.  
    51.     private float mMoveLen; 
    52.     /**
    53.      * 水波平移速度
    54.      */ 
    55.     public static final float SPEED = 1.7f; 
    56.  
    57.     private List<Point> mPointsList; 
    58.     private Paint mPaint; 
    59.     private Paint mTextPaint; 
    60.     private Path mWavePath; 
    61.     private boolean isMeasured = false
    62.  
    63.     private Timer timer; 
    64.     private MyTimerTask mTask; 
    65.     Handler updateHandler = new Handler() 
    66.     { 
    67.  
    68.         @Override 
    69.         public void handleMessage(Message msg) 
    70.         { 
    71.             // 记录平移总位移 
    72.             mMoveLen += SPEED; 
    73.             // 水位上升 
    74.             mLevelLine -= 0.1f; 
    75.             if (mLevelLine < 0
    76.                 mLevelLine = 0
    77.             mLeftSide += SPEED; 
    78.             // 波形平移 
    79.             for (int i = 0; i < mPointsList.size(); i++) 
    80.             { 
    81.                 mPointsList.get(i).setX(mPointsList.get(i).getX() + SPEED); 
    82.                 switch (i % 4
    83.                 { 
    84.                 case 0
    85.                 case 2
    86.                     mPointsList.get(i).setY(mLevelLine); 
    87.                     break
    88.                 case 1
    89.                     mPointsList.get(i).setY(mLevelLine + mWaveHeight); 
    90.                     break
    91.                 case 3
    92.                     mPointsList.get(i).setY(mLevelLine - mWaveHeight); 
    93.                     break
    94.                 } 
    95.             } 
    96.             if (mMoveLen >= mWaveWidth) 
    97.             { 
    98.                 // 波形平移超过一个完整波形后复位 
    99.                 mMoveLen = 0
    100.                 resetPoints(); 
    101.             } 
    102.             invalidate(); 
    103.         } 
    104.  
    105.     }; 
    106.  
    107.     /**
    108.      * 所有点的x坐标都还原到初始状态,也就是一个周期前的状态
    109.      */ 
    110.     private void resetPoints() 
    111.     { 
    112.         mLeftSide = -mWaveWidth; 
    113.         for (int i = 0; i < mPointsList.size(); i++) 
    114.         { 
    115.             mPointsList.get(i).setX(i * mWaveWidth / 4 - mWaveWidth); 
    116.         } 
    117.     } 
    118.  
    119.     public WaveView(Context context) 
    120.     { 
    121.         super(context); 
    122.         init(); 
    123.     } 
    124.  
    125.     public WaveView(Context context, AttributeSet attrs) 
    126.     { 
    127.         super(context, attrs); 
    128.         init(); 
    129.     } 
    130.  
    131.     public WaveView(Context context, AttributeSet attrs, int defStyle) 
    132.     { 
    133.         super(context, attrs, defStyle); 
    134.         init(); 
    135.     } 
    136.  
    137.     private void init() 
    138.     { 
    139.         mPointsList = new ArrayList<Point>(); 
    140.         timer = new Timer(); 
    141.  
    142.         mPaint = new Paint(); 
    143.         mPaint.setAntiAlias(true); 
    144.         mPaint.setStyle(Style.FILL); 
    145.         mPaint.setColor(Color.BLUE); 
    146.  
    147.         mTextPaint = new Paint(); 
    148.         mTextPaint.setColor(Color.WHITE); 
    149.         mTextPaint.setTextAlign(Align.CENTER); 
    150.         mTextPaint.setTextSize(30); 
    151.  
    152.         mWavePath = new Path(); 
    153.     } 
    154.  
    155.     @Override 
    156.     public void onWindowFocusChanged(boolean hasWindowFocus) 
    157.     { 
    158.         super.onWindowFocusChanged(hasWindowFocus); 
    159.         // 开始波动 
    160.         start(); 
    161.     } 
    162.  
    163.     private void start() 
    164.     { 
    165.         if (mTask != null
    166.         { 
    167.             mTask.cancel(); 
    168.             mTask = null
    169.         } 
    170.         mTask = new MyTimerTask(updateHandler); 
    171.         timer.schedule(mTask, 0, 10); 
    172.     } 
    173.  
    174.     @Override 
    175.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
    176.     { 
    177.         super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
    178.         if (!isMeasured) 
    179.         { 
    180.             isMeasured = true
    181.             mViewHeight = getMeasuredHeight(); 
    182.             mViewWidth = getMeasuredWidth(); 
    183.             // 水位线从最底下开始上升 
    184.             mLevelLine = mViewHeight; 
    185.             // 根据View宽度计算波形峰值 
    186.             mWaveHeight = mViewWidth / 2.5f; 
    187.             // 波长等于四倍View宽度也就是View中只能看到四分之一个波形,这样可以使起伏更明显 
    188.             mWaveWidth = mViewWidth * 4
    189.             // 左边隐藏的距离预留一个波形 
    190.             mLeftSide = -mWaveWidth; 
    191.             // 这里计算在可见的View宽度中能容纳几个波形,注意n上取整 
    192.             int n = (int) Math.round(mViewWidth / mWaveWidth + 0.5); 
    193.             // n个波形需要4n+1个点,但是我们要预留一个波形在左边隐藏区域,所以需要4n+5个点 
    194.             for (int i = 0; i < (4 * n + 5); i++) 
    195.             { 
    196.                 // 从P0开始初始化到P4n+4,总共4n+5个点 
    197.                 float x = i * mWaveWidth / 4 - mWaveWidth; 
    198.                 float y = 0
    199.                 switch (i % 4
    200.                 { 
    201.                 case 0
    202.                 case 2
    203.                     // 零点位于水位线上 
    204.                     y = mLevelLine; 
    205.                     break
    206.                 case 1
    207.                     // 往下波动的控制点 
    208.                     y = mLevelLine + mWaveHeight; 
    209.                     break
    210.                 case 3
    211.                     // 往上波动的控制点 
    212.                     y = mLevelLine - mWaveHeight; 
    213.                     break
    214.                 } 
    215.                 mPointsList.add(new Point(x, y)); 
    216.             } 
    217.         } 
    218.     } 
    219.  
    220.     @Override 
    221.     protected void onDraw(Canvas canvas) 
    222.     { 
    223.  
    224.         mWavePath.reset(); 
    225.         int i = 0
    226.         mWavePath.moveTo(mPointsList.get(0).getX(), mPointsList.get(0).getY()); 
    227.         for (; i < mPointsList.size() - 2; i = i + 2
    228.         { 
    229.             mWavePath.quadTo(mPointsList.get(i + 1).getX(), 
    230.                     mPointsList.get(i + 1).getY(), mPointsList.get(i + 2
    231.                             .getX(), mPointsList.get(i + 2).getY()); 
    232.         } 
    233.         mWavePath.lineTo(mPointsList.get(i).getX(), mViewHeight); 
    234.         mWavePath.lineTo(mLeftSide, mViewHeight); 
    235.         mWavePath.close(); 
    236.  
    237.         // mPaint的Style是FILL,会填充整个Path区域 
    238.         canvas.drawPath(mWavePath, mPaint); 
    239.         // 绘制百分比 
    240.         canvas.drawText("" + ((int) ((1 - mLevelLine / mViewHeight) * 100)) 
    241.                 + "%", mViewWidth / 2, mLevelLine + mWaveHeight 
    242.                 + (mViewHeight - mLevelLine - mWaveHeight) / 2, mTextPaint); 
    243.     } 
    244.  
    245.     class MyTimerTask extends TimerTask 
    246.     { 
    247.         Handler handler; 
    248.  
    249.         public MyTimerTask(Handler handler) 
    250.         { 
    251.             this.handler = handler; 
    252.         } 
    253.  
    254.         @Override 
    255.         public void run() 
    256.         { 
    257.             handler.sendMessage(handler.obtainMessage()); 
    258.         } 
    259.  
    260.     } 
    261.  
    262.     class Point 
    263.     { 
    264.         private float x; 
    265.         private float y; 
    266.  
    267.         public float getX() 
    268.         { 
    269.             return x; 
    270.         } 
    271.  
    272.         public void setX(float x) 
    273.         { 
    274.             this.x = x; 
    275.         } 
    276.  
    277.         public float getY() 
    278.         { 
    279.             return y; 
    280.         } 
    281.  
    282.         public void setY(float y) 
    283.         { 
    284.             this.y = y; 
    285.         } 
    286.  
    287.         public Point(float x, float y) 
    288.         { 
    289.             this.x = x; 
    290.             this.y = y; 
    291.         } 
    292.  
    293.     } 
    294.  


    代码中注释写的很多,不难看懂。

     

    Demo的布局:

     

    [html] view plaincopy
     
    1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    2.     android:layout_width="match_parent" 
    3.     android:layout_height="match_parent" 
    4.     android:background="#000000" > 
    5.  
    6.     <com.jingchen.waveview.WaveView 
    7.         android:layout_width="100dp" 
    8.         android:background="#ffffff" 
    9.         android:layout_height="match_parent" 
    10.         android:layout_centerInParent="true" /> 
    11.  
    12. </RelativeLayout> 


    MainActivity的代码:

     

     

    [java] view plaincopy
     
    1. package com.jingchen.waveview; 
    2.  
    3. import android.os.Bundle; 
    4. import android.app.Activity; 
    5. import android.view.Menu; 
    6.  
    7. public class MainActivity extends Activity 
    8.  
    9.     @Override 
    10.     protected void onCreate(Bundle savedInstanceState) 
    11.     { 
    12.         super.onCreate(savedInstanceState); 
    13.         setContentView(R.layout.activity_main); 
    14.     } 
    15.  
    16.     @Override 
    17.     public boolean onCreateOptionsMenu(Menu menu) 
    18.     { 
    19.         getMenuInflater().inflate(R.menu.main, menu); 
    20.         return true
    21.     } 
    22.  

    代码量很少。这样就可以很简单的做出水波效果啦~

    源码下载

  • 相关阅读:
    nginx平滑升级及回滚
    redis源码安装
    memcached安装
    Harbor源码部署
    Maven源码部署
    tomcat单机多实例(未完待续)
    部署tomcat
    nginx编译参数详解
    CentOS7 安装pip/pip3
    nginx 部署配置
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/4510953.html
Copyright © 2011-2022 走看看