实现波浪效果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;@Overridepublic 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);}@Overridevoid init() {perRadian = (float) (2 * Math.PI * period / mWidth);speedRadian = (float) (speedPeriod * Math.PI * 2);offRadian = (float) (offset * 2 * Math.PI);}@Overridepublic 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;@Overridepublic 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);}@Overridevoid init() {periodWidth = mWidth / period;speedWidth = speedPeriod * periodWidth;currentOffset = offset * periodWidth;conY = (float) (mSwing / MAX_Y);isReInit = false;}@Overridepublic 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

转载于:https://www.cnblogs.com/sskbskdrin/p/10511878.html

自定义view 波浪效果相关推荐

  1. [Android]自定义View带效果的滚动数字

    [Android]自定义View带效果的滚动数字 @Author GQ 2016年07月29日 一个可以让数字滚动的View,可以自定义参数,是想要的那种效果! 原文github地址 效果图 Andr ...

  2. android 自定义view 动画效果,Android自定义view----音乐播放动画

    先给大家看一下效果,因为我也不知道这个东西具体叫什么,标题上面写的是"音乐播放动画",可能描述的不太准确. 效果图.gif 前言 最近项目中做了一个音频播放的功能,播放条上需要一个 ...

  3. android 自定义view 动画效果,Android自定义view实现阻尼效果的加载动画

    效果: 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又 ...

  4. 自定义view 落叶效果

    实现树叶飘落的一个自定义view  实际项目中几乎没有这个需求,但是做出来可以学习进步,有兴趣的童鞋可以看看. 先看图吧: (可能需要少许时间加载,稍等..) 就是这么一个效果. 主要涉及到canva ...

  5. Android 自定义View 时钟效果

    Android 自定义时钟浅析 最有趣,最好玩的东西,一定是高度灵活的东西,今天我们以自定义View的方式来实现一个表盘样式的时钟,并自动设置为当前时间,因为感觉网上的直接能运行的代码很少,思路也很麻 ...

  6. html5制作波浪,技能get:用HTML5实现波浪效果

    rr Document .box{ width: 500px; height: 500px; margin:100px auto; background:hotpink; border-radius: ...

  7. 仿海报工厂效果的自定义View

    本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 近期做了一个自定义View,效果有些类似海报工厂,先看下效果图: 就是一个背景图,中间挖了若干个形状不同的"洞" ...

  8. 仿海报工厂效果的自定义View(在图片上输入文字)

    下面这个view来自"易水南风",我在其代码上加入了文字的输入 下载地址 https://github.com/yxkrrhx/PotserView 近期做了一个自定义View,效 ...

  9. 自定义view - 收藏集 - 掘金

    Android 从 0 开始自定义控件之 View 的 draw 过程 (九) - Android - 掘金 转载请标明出处: http://blog.csdn.net/airsaid/... 本文出 ...

  10. 嵌套RecyclerView左右滑动替代自定义view

    以前的左右滑动效果采用自定义scrollview或者linearlayout来实现,recyclerview可以很好的做这个功能,一般的需求就是要么一个独立的左右滑动效果,要么在一个列表里的中间部分一 ...

最新文章

  1. STM32配置一般过程(持续更新中)
  2. 观点 | 港科大张潼教授最新发言:对人工智能发展的一些思考
  3. django-Modelform
  4. C# Dictionary.Add(key,123) 与 Dictionary[key]=123的区别
  5. mysql联表查询多记录显示_数据库:MySQL(多表的表记录的查询)(三)
  6. Python3压缩和解压缩实现
  7. PIC单片机入门_异步通讯模式详解
  8. java 调用tomcat api,Tomcat采用双向认证https协议通过JavaAPI调用(一)配置SSL
  9. oracle12c之 控制pdb中sga 与 pga 内存使用
  10. P3348-[ZJOI2016]大森林【LCT】
  11. Java飞行记录器(JFR)
  12. oracle监听启动命令6,[转] oracle 监听
  13. 虚拟机查看cpu型号_CentOS7安装KVM虚拟机
  14. 世界笔记本巨头厂商 Compal 被勒索1700万美元
  15. 初中节点法分析电路_初三物理电路图解题思路:电路简化原理
  16. T1118,T1677,T1122
  17. 一个屌丝程序猿的人生(八)
  18. Linux中EXT3与EXT4的区别!
  19. 求3000以内的全部亲密数。
  20. 自动驾驶中结构化BEV交通场景的理解(ICCV 2021)

热门文章

  1. 查询命令从数据库中查询出名为NAME、usertype、LENGTH的三列,同时还要输出外码名FK_Tno和主表名Dept以及表名Teacher、主码名PK_Dno和表名...
  2. vue移动端项目真机调试
  3. 百度语音sdk集成java,[专栏作家]百度语音识别接入【Eclipse+Unity3D】
  4. Cesium 三维漫游
  5. vos3000源码(各个版本机器人部署)
  6. 服务器稳定对页面的排名很重要,网站关键词排名不稳定怎么办
  7. Docker系列之Docker容器(读书笔记)
  8. android uri图片压缩,android – 如何将Uri图像压缩为位图
  9. STL中map、set的数据结构及底层实现
  10. 永远不要小瞧任何一个人