zoukankan      html  css  js  c++  java
  • 自定义view 波浪效果

    实现波浪效果view,可以自定义view,也可以自定义drawable,我个人比较喜欢重写drawable,因此这里是自定义drawable实现效果,费话少说,先看效果。效果图

    这里用了两种方式实现波浪效果,一种是通过正弦函数去画路径,一种是通过三阶贝塞尔曲线画出类似正弦曲线的效果
    先看看实现波浪效果需要用到的一些参数,看注释大概就能了解

    /**
    * 画布的宽
    */
    int mWidth;
    /**
    * 画布的高
    */
    int mHeight;
    /**
    * 初始偏移量
    */
    float offset = 0;
    /**
    * 线的宽度,当lineWidth>0时,是画线模式,否则是填充模式
    */
    float lineWidth = 0;
    /**
    * 显示的周期数
    */
    float period = 1;
    /**
    * 移动速度,每秒钟移动的周期数
    */
    float speedPeriod = 0.5f;
    /**
    * 波浪的振幅,单位px
    */
    float mSwing = 20;

    再来看看正弦函数的实现方式

    private class WaveSin extends Wave {
    
        /**
         * 初始偏移量
         */
        float offRadian = 0;
        /**
         * 每个像素占的弧度
         */
        double perRadian;
        /**
         * 每秒移动的弧度数
         */
        float speedRadian;
    
        @Override
        public void onDraw(Canvas canvas, boolean isBottom) {
            float y = mHeight;
            mPath.reset();
            //计算路径点的初始位置
            if (lineWidth > 0) {
                y = (float) (mSwing * Math.sin(offRadian) + mSwing);
                mPath.moveTo(-lineWidth, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
            } else {
                mPath.moveTo(0, isBottom ? 0 : mHeight);
            }
    
            //步长越小越精细,当然越消耗cpu性能,过大则会有锯齿
            int step = mWidth / 100 > 20 ? 20 : mWidth / 100;
    
            //通过正弦函数计算路径点,放入mPath中
            for (int x = 0; x <= mWidth + step; x += step) {
                y = (float) (mSwing * Math.sin(perRadian * x + offRadian) + mSwing);
                mPath.lineTo(x, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
            }
    
            //填充模式时,画完完整路径
            if (lineWidth <= 0) {
                mPath.lineTo(mWidth, isBottom ? mHeight - y : y);
                mPath.lineTo(mWidth, isBottom ? 0 : mHeight);
                mPath.lineTo(0, isBottom ? 0 : mHeight);
                mPath.close();
            }
    
            canvas.drawPath(mPath, mPaint);
        }
    
        @Override
        void init() {
            perRadian = (float) (2 * Math.PI * period / mWidth);
            speedRadian = (float) (speedPeriod * Math.PI * 2);
            offRadian = (float) (offset * 2 * Math.PI);
        }
    
        @Override
        public void move(float delta) {
            offRadian += speedRadian * delta;
        }
    }

    首先`init()`方法中,perRadian是计算每弧度所占的宽度,speedRadian计算每秒移动的弧度,offRadian是当前偏移弧度,在`move(float delta)`中可以看到delta是时间变化量,所以
    `下一次的偏移量 = 当前偏移量+每秒移动的弧度*时间的变化量`,即`offRadian += speedRadian * delta;`
    再来看看主要的onDraw方法,Canvas是画布,isBottom是指波浪是否在整个画布的底部。

    下面是通过贝塞尔曲线实现波浪效果

    private class WaveBezier extends Wave {
        /**
         * 根据贝塞尔曲线公式计算的一个常量值
         */
        private static final double MAX_Y = 0.28867513459481287;
        /**
         * 一个周期的宽度
         */
        float periodWidth;
        /**
         * 每秒钟移动的宽度
         */
        float speedWidth;
        /**
         * 贝塞尔曲线控制点的Y轴坐标
         */
        float conY;
        /**
         * 当前偏移量
         */
        float currentOffset = 0;
    
        @Override
        public void onDraw(Canvas canvas, boolean isBottom) {
            mPath.reset();
            //  移动到第一个周期的起始点
            mPath.moveTo(-currentOffset, 0);
            float conX = periodWidth / 2;
            int w = (int) -currentOffset;
            for (int i = 0; i <= mWidth + currentOffset; i += periodWidth) {
                mPath.rCubicTo(conX, conY, conX, -conY, periodWidth, 0);//注意,这里用的是相对坐标
                w += periodWidth;
            }
    
            // 闭合路径
            if (lineWidth <= 0) {
                mPath.rLineTo(0, isBottom ? -mHeight : mHeight);
                mPath.rLineTo(-w, 0);
                mPath.close();
            }
    
            //  对Y轴整体偏移
            mPath.offset(0, (isBottom ? mHeight - mSwing - lineWidth / 2 : mSwing + lineWidth / 2));
    
            canvas.drawPath(mPath, mPaint);
        }
    
        @Override
        void init() {
            periodWidth = mWidth / period;
            speedWidth = speedPeriod * periodWidth;
            currentOffset = offset * periodWidth;
            conY = (float) (mSwing / MAX_Y);
            isReInit = false;
        }
    
        @Override
        public void move(float delta) {
            if (periodWidth <= 0) {
                isReInit = true;
                return;
            }
            currentOffset += speedWidth * delta;
            if (currentOffset < 0) {
                currentOffset += periodWidth;
            } else {
                if (currentOffset > periodWidth) {
                    currentOffset -= periodWidth;
                }
            }
        }
    }

    在 `init()`方法中periodWidth为单个周期宽度,speedWidth为每秒移动的宽度,currentOffset为当前偏移量,conY为控制点的Y轴坐标。

    最后贴上完整代码

      1 package cn.sskbskdrin.wave;
      2 
      3 import android.animation.ValueAnimator;
      4 import android.graphics.Canvas;
      5 import android.graphics.ColorFilter;
      6 import android.graphics.Paint;
      7 import android.graphics.Path;
      8 import android.graphics.PixelFormat;
      9 import android.graphics.Rect;
     10 import android.graphics.drawable.Animatable;
     11 import android.graphics.drawable.Drawable;
     12 import android.view.animation.LinearInterpolator;
     13 
     14 import java.util.ArrayList;
     15 import java.util.List;
     16 import java.util.Map;
     17 import java.util.WeakHashMap;
     18 
     19 /**
     20  * Created by sskbskdrin on 2018/4/4.
     21  *
     22  * @author sskbskdrin
     23  */
     24 public class WaveDrawable extends Drawable implements Animatable {
     25 
     26     private final List<Wave> list;
     27 
     28     private int mWidth;
     29     private int mHeight;
     30 
     31     private boolean animIsStart = false;
     32 
     33     private boolean isBottom = false;
     34 
     35     public WaveDrawable() {
     36         this(1);
     37     }
     38 
     39     public WaveDrawable(int count) {
     40         this(count, false);
     41     }
     42 
     43     public WaveDrawable(int count, boolean isSin) {
     44         if (count <= 0) {
     45             throw new IllegalArgumentException("Illegal count: " + count);
     46         }
     47         list = new ArrayList<>(count);
     48         for (int i = 0; i < count; i++) {
     49             list.add(isSin ? new WaveSin() : new WaveBezier());
     50         }
     51     }
     52 
     53     public void setBottom(boolean isBottom) {
     54         this.isBottom = isBottom;
     55     }
     56 
     57     /**
     58      * 设置填充的颜色
     59      *
     60      * @param color
     61      */
     62     public void setColor(int color) {
     63         for (Wave wave : list) {
     64             wave.setColor(color);
     65         }
     66     }
     67 
     68     /**
     69      * 设置填充的颜色
     70      *
     71      * @param color
     72      */
     73     public void setColor(int color, int index) {
     74         if (index < list.size()) {
     75             list.get(index).setColor(color);
     76         }
     77     }
     78 
     79     public void setOffset(float offset) {
     80         for (Wave wave : list) {
     81             wave.offset(offset);
     82         }
     83     }
     84 
     85     /**
     86      * 设置初始相位
     87      *
     88      * @param offset
     89      * @param index
     90      */
     91     public void setOffset(float offset, int index) {
     92         if (index < list.size()) {
     93             list.get(index).offset(offset);
     94         }
     95     }
     96 
     97     /**
     98      * 波浪的大小
     99      *
    100      * @param swing
    101      */
    102     public void setSwing(int swing) {
    103         for (Wave wave : list) {
    104             wave.setSwing(swing);
    105         }
    106     }
    107 
    108     /**
    109      * 波浪的大小
    110      *
    111      * @param swing
    112      * @param index
    113      */
    114     public void setSwing(int swing, int index) {
    115         checkIndex(index);
    116         list.get(index).setSwing(swing);
    117     }
    118 
    119     /**
    120      * 设置波浪流动的速度
    121      *
    122      * @param speed
    123      */
    124     public void setSpeed(float speed) {
    125         for (Wave wave : list) {
    126             wave.setSpeed(speed);
    127         }
    128     }
    129 
    130     /**
    131      * 设置波浪流动的速度
    132      *
    133      * @param speed
    134      */
    135     public void setSpeed(float speed, int index) {
    136         checkIndex(index);
    137         list.get(index).setSpeed(speed);
    138     }
    139 
    140     /**
    141      * 设置波浪周期数
    142      *
    143      * @param period (0,--)
    144      */
    145     public void setPeriod(float period) {
    146         for (Wave wave : list) {
    147             wave.setPeriod(period);
    148         }
    149     }
    150 
    151     public void setPeriod(float period, int index) {
    152         checkIndex(index);
    153         list.get(index).setPeriod(period);
    154     }
    155 
    156     private void checkIndex(int index) {
    157         if (index < 0 || index >= list.size()) {
    158             throw new IllegalArgumentException("Illegal index. list size=" + list.size() + " index=" + index);
    159         }
    160     }
    161 
    162     public void setLineWidth(float width) {
    163         for (Wave wave : list) {
    164             wave.setLineWidth(width);
    165         }
    166     }
    167 
    168     public void setLineWidth(float width, int index) {
    169         if (index >= 0 && index < list.size()) {
    170             list.get(index).setLineWidth(width);
    171         }
    172     }
    173 
    174     @Override
    175     protected void onBoundsChange(Rect bounds) {
    176         mWidth = bounds.width();
    177         mHeight = bounds.height();
    178         for (Wave wave : list) {
    179             wave.onSizeChange(mWidth, mHeight);
    180         }
    181     }
    182 
    183     @Override
    184     public void draw(Canvas canvas) {
    185         for (Wave wave : list) {
    186             if (wave.isReInit) {
    187                 wave.init();
    188                 wave.isReInit = false;
    189             }
    190             wave.onDraw(canvas, isBottom);
    191         }
    192     }
    193 
    194     @Override
    195     public int getIntrinsicWidth() {
    196         return mWidth;
    197     }
    198 
    199     @Override
    200     public int getIntrinsicHeight() {
    201         return mHeight;
    202     }
    203 
    204     private void move(float delta) {
    205         for (Wave wave : list) {
    206             wave.move(delta);
    207         }
    208     }
    209 
    210     @Override
    211     public void setAlpha(int alpha) {
    212         for (Wave wave : list) {
    213             wave.mPaint.setAlpha(alpha);
    214         }
    215     }
    216 
    217     @Override
    218     public void setColorFilter(ColorFilter cf) {
    219         for (Wave wave : list) {
    220             wave.mPaint.setColorFilter(cf);
    221         }
    222     }
    223 
    224     @Override
    225     public int getOpacity() {
    226         return PixelFormat.TRANSLUCENT;
    227     }
    228 
    229     @Override
    230     public boolean setVisible(boolean visible, boolean restart) {
    231         if (visible) {
    232             if (animIsStart) {
    233                 AnimateListener.start(this);
    234             }
    235         } else {
    236             if (animIsStart) {
    237                 AnimateListener.start(this);
    238             }
    239         }
    240         return super.setVisible(visible, restart);
    241     }
    242 
    243     @Override
    244     public void start() {
    245         animIsStart = true;
    246         AnimateListener.start(this);
    247     }
    248 
    249     @Override
    250     public void stop() {
    251         AnimateListener.cancel(this);
    252         animIsStart = false;
    253     }
    254 
    255     @Override
    256     public boolean isRunning() {
    257         return AnimateListener.isRunning(this);
    258     }
    259 
    260     private static class AnimateListener implements ValueAnimator.AnimatorUpdateListener {
    261         private static WeakHashMap<WaveDrawable, Boolean> map = new WeakHashMap<>();
    262         private static int lastTime = 0;
    263         private static ValueAnimator valueAnimator;
    264 
    265         private static void initAnimation() {
    266             valueAnimator = ValueAnimator.ofInt(0, 1000);
    267             valueAnimator.setDuration(1000);
    268             valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
    269             valueAnimator.setInterpolator(new LinearInterpolator());
    270             valueAnimator.addUpdateListener(new AnimateListener());
    271         }
    272 
    273         private static void start(WaveDrawable drawable) {
    274             if (!map.containsKey(drawable)) {
    275                 map.put(drawable, true);
    276             }
    277             if (valueAnimator == null) {
    278                 initAnimation();
    279             }
    280             if (!valueAnimator.isRunning()) {
    281                 valueAnimator.start();
    282             }
    283         }
    284 
    285         private static void cancel(WaveDrawable drawable) {
    286             if (map.containsKey(drawable)) {
    287                 map.put(drawable, false);
    288             }
    289         }
    290 
    291         private static boolean isRunning(WaveDrawable drawable) {
    292             return map.containsKey(drawable) && map.get(drawable);
    293         }
    294 
    295         @Override
    296         public void onAnimationUpdate(ValueAnimator animation) {
    297             int current = (int) animation.getAnimatedValue();
    298             int delta = current - lastTime;
    299             if (delta < 0) {
    300                 delta = current + 1000 - lastTime;
    301             }
    302             float deltaF = delta / 1000f;
    303             lastTime = current;
    304             if (map.size() == 0) {
    305                 animation.cancel();
    306                 valueAnimator = null;
    307                 return;
    308             }
    309             for (Map.Entry<WaveDrawable, Boolean> wave : map.entrySet()) {
    310                 if (wave != null && wave.getValue()) {
    311                     WaveDrawable drawable = wave.getKey();
    312                     drawable.move(deltaF);
    313                     drawable.invalidateSelf();
    314                 }
    315             }
    316         }
    317     }
    318 
    319     private abstract class Wave {
    320 
    321         /**
    322          * 画布的宽
    323          */
    324         int mWidth;
    325         /**
    326          * 画布的高
    327          */
    328         int mHeight;
    329         Path mPath = new Path();
    330         Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    331         /**
    332          * 初始偏移量
    333          */
    334         float offset = 0;
    335         /**
    336          * 线的宽度,当lineWidth>0时,是画线模式,否则是填充模式
    337          */
    338         float lineWidth = 0;
    339         /**
    340          * 显示的周期数
    341          */
    342         float period = 1;
    343         /**
    344          * 移动速度,每秒钟移动的周期数
    345          */
    346         float speedPeriod = 0.5f;
    347         /**
    348          * 波浪的振幅,单位px
    349          */
    350         float mSwing = 20;
    351 
    352         boolean isReInit = true;
    353 
    354         /**
    355          * drawable 大小改变
    356          *
    357          * @param width
    358          * @param height
    359          */
    360         void onSizeChange(int width, int height) {
    361             mWidth = width;
    362             mHeight = height;
    363             isReInit = true;
    364         }
    365 
    366         abstract void onDraw(Canvas canvas, boolean isBottom);
    367 
    368         abstract void init();
    369 
    370         /**
    371          * 移动的时间变化量
    372          *
    373          * @param delta
    374          */
    375         abstract void move(float delta);
    376 
    377         /**
    378          * 设置线的宽度
    379          *
    380          * @param width
    381          */
    382         void setLineWidth(float width) {
    383             lineWidth = width;
    384             if (lineWidth > 0) {
    385                 mPaint.setStyle(Paint.Style.STROKE);
    386                 mPaint.setStrokeWidth(lineWidth);
    387             } else {
    388                 mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    389             }
    390             isReInit = true;
    391         }
    392 
    393         void setColor(int color) {
    394             mPaint.setColor(color);
    395         }
    396 
    397         /**
    398          * 每秒移动的像素数
    399          *
    400          * @param speedPeriod
    401          */
    402         void setSpeed(float speedPeriod) {
    403             this.speedPeriod = speedPeriod;
    404             isReInit = true;
    405         }
    406 
    407         /**
    408          * 振幅大小
    409          *
    410          * @param swing
    411          */
    412         void setSwing(float swing) {
    413             if (swing <= 0) {
    414                 throw new IllegalArgumentException("Illegal swing: " + swing);
    415             }
    416             mSwing = swing;
    417             isReInit = true;
    418         }
    419 
    420         /**
    421          * 显示周期数
    422          *
    423          * @param period
    424          */
    425         void setPeriod(float period) {
    426             if (period <= 0) {
    427                 throw new IllegalArgumentException("Illegal period: " + period);
    428             }
    429             this.period = period;
    430             isReInit = true;
    431         }
    432 
    433         /**
    434          * 起始偏移量
    435          *
    436          * @param offPeriod
    437          */
    438         void offset(float offPeriod) {
    439             this.offset = offPeriod;
    440             isReInit = true;
    441         }
    442     }
    443 
    444     private class WaveSin extends Wave {
    445 
    446         /**
    447          * 初始偏移量
    448          */
    449         float offRadian = 0;
    450         /**
    451          * 每个像素占的弧度
    452          */
    453         double perRadian;
    454         /**
    455          * 每秒移动的弧度数
    456          */
    457         float speedRadian;
    458 
    459         @Override
    460         public void onDraw(Canvas canvas, boolean isBottom) {
    461             float y = mHeight;
    462             mPath.reset();
    463             //计算路径点的初始位置
    464             if (lineWidth > 0) {
    465                 y = (float) (mSwing * Math.sin(offRadian) + mSwing);
    466                 mPath.moveTo(-lineWidth, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
    467             } else {
    468                 mPath.moveTo(0, isBottom ? 0 : mHeight);
    469             }
    470 
    471             //步长越小越精细,当然越消耗cpu性能,过大则会有锯齿
    472             int step = mWidth / 100 > 20 ? 20 : mWidth / 100;
    473 
    474             //通过正弦函数计算路径点,放入mPath中
    475             for (int x = 0; x <= mWidth + step; x += step) {
    476                 y = (float) (mSwing * Math.sin(perRadian * x + offRadian) + mSwing);
    477                 mPath.lineTo(x, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
    478             }
    479 
    480             //填充模式时,画完完整路径
    481             if (lineWidth <= 0) {
    482                 mPath.lineTo(mWidth, isBottom ? mHeight - y : y);
    483                 mPath.lineTo(mWidth, isBottom ? 0 : mHeight);
    484                 mPath.lineTo(0, isBottom ? 0 : mHeight);
    485                 mPath.close();
    486             }
    487 
    488             canvas.drawPath(mPath, mPaint);
    489         }
    490 
    491         @Override
    492         void init() {
    493             perRadian = (float) (2 * Math.PI * period / mWidth);
    494             speedRadian = (float) (speedPeriod * Math.PI * 2);
    495             offRadian = (float) (offset * 2 * Math.PI);
    496         }
    497 
    498         @Override
    499         public void move(float delta) {
    500             offRadian += speedRadian * delta;
    501         }
    502     }
    503 
    504     private class WaveBezier extends Wave {
    505         /**
    506          * 根据贝塞尔曲线公式计算的一个常量值
    507          */
    508         private static final double MAX_Y = 0.28867513459481287;
    509         /**
    510          * 一个周期的宽度
    511          */
    512         float periodWidth;
    513         /**
    514          * 每秒钟移动的宽度
    515          */
    516         float speedWidth;
    517         /**
    518          * 贝塞尔曲线控制点的Y轴坐标
    519          */
    520         float conY;
    521         /**
    522          * 当前偏移量
    523          */
    524         float currentOffset = 0;
    525 
    526         @Override
    527         public void onDraw(Canvas canvas, boolean isBottom) {
    528             mPath.reset();
    529             //  移动到第一个周期的起始点
    530             mPath.moveTo(-currentOffset, 0);
    531             float conX = periodWidth / 2;
    532             int w = (int) -currentOffset;
    533             for (int i = 0; i <= mWidth + currentOffset; i += periodWidth) {
    534                 mPath.rCubicTo(conX, conY, conX, -conY, periodWidth, 0);//注意,这里用的是相对坐标
    535                 w += periodWidth;
    536             }
    537 
    538             // 闭合路径
    539             if (lineWidth <= 0) {
    540                 mPath.rLineTo(0, isBottom ? -mHeight : mHeight);
    541                 mPath.rLineTo(-w, 0);
    542                 mPath.close();
    543             }
    544 
    545             //  对Y轴整体偏移
    546             mPath.offset(0, (isBottom ? mHeight - mSwing - lineWidth / 2 : mSwing + lineWidth / 2));
    547 
    548             canvas.drawPath(mPath, mPaint);
    549         }
    550 
    551         @Override
    552         void init() {
    553             periodWidth = mWidth / period;
    554             speedWidth = speedPeriod * periodWidth;
    555             currentOffset = offset * periodWidth;
    556             conY = (float) (mSwing / MAX_Y);
    557             isReInit = false;
    558         }
    559 
    560         @Override
    561         public void move(float delta) {
    562             if (periodWidth <= 0) {
    563                 isReInit = true;
    564                 return;
    565             }
    566             currentOffset += speedWidth * delta;
    567             if (currentOffset < 0) {
    568                 currentOffset += periodWidth;
    569             } else {
    570                 if (currentOffset > periodWidth) {
    571                     currentOffset -= periodWidth;
    572                 }
    573             }
    574         }
    575     }
    576 }
    View Code
  • 相关阅读:
    v-charts修改点击图例事件,legendselectchanged
    十分钟了解HTTPS
    VUE2+elementUI前端实现 三级省市联动select
    总结前端面试过程中最容易出现的问题
    JS实现快速排序,冒泡排序
    前端面试偏门题总结
    从输入URL到页面加载的过程?如何由一道题完善自己的前端知识体系!
    Ant通过build-war.xml把一个web项目打包成war
    web项目的.classpath和.project详解
    联合主键三种实现方式
  • 原文地址:https://www.cnblogs.com/sskbskdrin/p/10511878.html
Copyright © 2011-2022 走看看