自定义view 波浪效果
程序员文章站
2022-07-05 15:41:01
实现波浪效果view,可以自定义view,也可以自定义drawable,我个人比较喜欢重写drawable,因此这里是自定义drawable实现效果,费话少说,先看效果。 这里用了两种方式实现波浪效果,一种是通过正弦函数去画路径,一种是通过三阶贝塞尔曲线画出类似正弦曲线的效果先看看实现波浪效果需要用 ......
实现波浪效果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 }
上一篇: Html5 Canvas介绍
下一篇: Z字形编排问题详解(C++)