欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

自定义view 波浪效果

程序员文章站 2022-03-12 09:32:30
实现波浪效果view,可以自定义view,也可以自定义drawable,我个人比较喜欢重写drawable,因此这里是自定义drawable实现效果,费话少说,先看效果。 这里用了两种方式实现波浪效果,一种是通过正弦函数去画路径,一种是通过三阶贝塞尔曲线画出类似正弦曲线的效果先看看实现波浪效果需要用 ......

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

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

/**
* 画布的宽
*/
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轴坐标。

最后贴上完整代码

自定义view 波浪效果
  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