實現波浪效果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