当前位置: 移动技术网 > 移动技术>移动开发>Android > 自定义view 波浪效果

自定义view 波浪效果

2019年03月12日  | 移动技术网移动技术  | 我要评论

实现波浪效果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

 

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网