(本博客为原创:https://cloud.tencent.com/developer/user/1148436/activities)
目录:
效果展示
感想
代码拆解
开源地址
效果展示
有没有兴趣继续看下去,直接看下"颜值"是第一步了。依次对应:下雨,飘雪,红包雨,碰撞球
16年总算过去了,跟各位猿友有说句祝福吧,新的一年少加点班,身体健康,钱能赚多少就尽量赚。
之前看博客园,很多发纯感想的,都被推荐了好几天,说实话,我几乎一拉到底,就看有没有点↓的。
公司放假时间是在23号,也就明天了,大四实习到现在,一直很忙,这段时间也是我在编程层面上学到了比较多东西的阶段,上面的自定义View是Android的,完成它们是在实习上班期间挤出时间做的,最初的初衷是想把第四个碰撞球的效果加入到毕设里面,现在总算是实现了,过程遇到很多问题,球体的碰撞处理比想象中麻烦很多,前三个比较简单,也是微信,QQ的下表情原理吧,我猜应该是。。。
毕设最终也会开源,还请大家留意我 GitHbub,这将会是一个集合非第三方IM和仿朋友圈+golang制作服务端等等知识的社交APP。
代码拆解
如果你仔细看了上面的四张效果图,你会发现,前三张是没碰撞效果处理的,而第四张是具备的。这也是我要区分实现的效果,目的是为了表明,不仅可以不碰撞还可以选择碰。
同时,飘雪和红包雨,事实也仅仅是图片的不同,这就对了。你只需要修改图片就能实现完全自定义,爱下什么下什么。
言归正传,整体使用了 适配器设计模式。代码是很简练易懂的,可以看看我的目录结构。
基类是一个暴露绘制和逻辑抽象方法的View子类,所有自定义View需要继承它。子类只需要关注自己要绘制什么,以及我要绘制的东西怎么去不断地改变,逻辑改变设计在一个 Thread 线程里面,采用 postInvalidate 通知 UI 刷新。
1 /**
2 * Created by LinGuanHong on 2017/1/15.
3 *
4 * My GitHub : https://github.com/af913337456/
5 *
6 * My Blog : http://www.cnblogs.com/linguanh/
7 *
8 */
9
10 public abstract class BaseView extends View {
11
12 protected String TAG = "zzzzz";
13 private static final int sleepTime = 30;
14 private RefreshThread refreshThread = null;
15
16 public BaseView(Context context) {
17 super(context);
18 }
19
20 public BaseView(Context context, AttributeSet attrs) {
21 super(context, attrs);
22 }
23
24 public abstract void drawSub(Canvas canvas);
25
26 public abstract void baseInit(int width,int height);
27
28 public abstract void logic();
29
30 @Override
31 protected final void onDraw(Canvas canvas) {
32 if(refreshThread == null){
33 refreshThread = new RefreshThread();
34 refreshThread.start();
35 }else{
36 drawSub(canvas);
37 }
38 }
39
40 @Override
41 protected void onDetachedFromWindow() {
42 running = false;
43 super.onDetachedFromWindow();
44 }
45
46 private boolean running = true;
47 private class RefreshThread extends Thread{
48
49 @Override
50 public void run() {
51 baseInit(getWidth(),getHeight());
52 while (running){
53 try{
54 logic();
55 postInvalidate();
56 Thread.sleep(sleepTime);
57 }catch (Exception e){
58 Log.d(TAG,e.toString());
59 }
60 }
61 }
62 }
63 }
BaseView 已经是一个可以直接继承的父类了,如果仅仅只是绘制一些比较简单逻辑的自定义View,继承它足以,但是要实现上述的效果,还需要添加多一个抽象类,无论是雨景还是雪景,它们都是个体的集合,也就是说,我们需要一个"景"的抽象。
ShowView 是一个具备泛型的抽象类,它是景色的制作者,至于是什么景,由你来决定,也就是泛型的传入。例如,我要制造雨景,那么我就传入雨点,雪景就是雪块
1 /**
2 * Created by LinGuanHong on 2017/1/15.
3 *
4 * My GitHub : https://github.com/af913337456/
5 *
6 * My Blog : http://www.cnblogs.com/linguanh/
7 *
8 */
9
10 public abstract class ShowView<T extends BaseItem> extends BaseView {
11
12 protected List<T> itemList = new ArrayList<>();
13 protected int size = 1;
14
15 public ShowView(Context context) {
16 super(context);
17 }
18
19 /** 子类实现布局必须要重写这个构造方法 */
20 public ShowView(Context context, AttributeSet attrs) {
21 super(context, attrs);
22 }
23
24 @Override
25 public void drawSub(Canvas canvas) {
26 for(T t:itemList){
27 t.draw(canvas);
28 }
29 }
30
31 @Override
32 public void logic() {
33 beforeLogicLoop();
34 for(T t:itemList){
35 t.move();
36 }
37 }
38
39 public abstract void beforeLogicLoop();
40 public abstract T getItem(int width, int height,Resources resources);
41 public abstract int getCount();
42
43 @Override
44 public void baseInit(int width, int height) {
45 size = getCount();
46 Resources resources = getResources();
47 for(int i = 0; i< size; i++){
48 itemList.add(getItem(width,height,resources));
49 }
50 }
51
52 }
由于我们的景色里面的个体可能是各种各样,那么为了使他们都能具备一些公共的属性,需要再抽象一个基础的个体类,共不同的景色个体继承。
1 /**
2 * Created by LinGuanHong on 2017/1/15.
3 *
4 * My GitHub : https://github.com/af913337456/
5 *
6 * My Blog : http://www.cnblogs.com/linguanh/
7 *
8 * 公共的属性和行为
9 *
10 */
11
12 public abstract class BaseItem {
13
14 protected int width,height; /** 景内宽高 */
15 protected Resources resources;
16
17 public BaseItem(int width,int height,Resources resources){
18 this.width = width;
19 this.height = height;
20 this.resources = resources;
21 }
22
23 public abstract void draw(Canvas canvas); /** 显示 */
24 public abstract void move(); /** 运动 */
25
26 }
OK,到这里基础的类都搞定了,为什么说是适配器模式呢,其实 BaseItem 就是 ViewHolder,ShowView 是 BaseAdapter,下面放下雨的Item 类和雨景。
注意注释,代码很简练易懂!
1 /**
2 * Created by LinGuanHong on 2017/1/15.
3 *
4 * 造雨,造多少个,160个,具体是什么雨,交给 item 实现
5 *
6 */
7
8 public class RainView extends ShowView<RainItem> {
9
10
11 public RainView(Context context) {
12 super(context);
13 }
14
15 public RainView(Context context, AttributeSet attrs) {
16 super(context, attrs);
17 }
18
19 @Override
20 public void beforeLogicLoop() {
21
22 }
23
24 @Override
25 public RainItem getItem(int width, int height, Resources resources) {
26 return new RainItem(width,height,resources); /** 要造的雨,是什么雨就在这里传入 */
27 }
28
29 @Override
30 public int getCount() { /** 要制作的雨数目 */
31 return 160;
32 }
33 }
我要制作的雨,随机数可以自定义。
1 /**
2 * Created by LinGuanHong on 2017/1/15.
3 */
4
5 public class RainItem extends BaseItem {
6
7 private float opt;
8 private int sizeX,sizeY; /** 充当角度 */
9 private int startX,startY,stopX,stopY;
10 private Paint paint;
11 private Random random;
12
13 public RainItem(int width, int height, Resources resources) {
14 super(width,height,resources);
15 init();
16 loopInit();
17 }
18
19 @Override
20 public void move() {
21 startX += sizeX * opt;
22 stopX += sizeX * opt;
23
24 startY += sizeY * opt;
25 stopY += sizeY * opt;
26 if(startY > height){
27 loopInit();
28 }
29 }
30
31 @Override
32 public void draw(Canvas canvas) {
33 Log.d("zzzzz","drawView "+startX+" "+startY+" "+stopX+" "+stopY);
34 canvas.drawLine(startX,startY,stopX,stopY,paint);
35 }
36
37 private void loopInit(){
38 sizeX = 1 + random.nextInt(10);
39 sizeY = 10 + random.nextInt(20);
40
41 startX = random.nextInt(width );
42 startY = random.nextInt(height);
43
44 opt = 0.2f + random.nextFloat();
45
46 stopX = startX + sizeX;
47 stopY = startY + sizeY;
48 }
49
50 private void init(){
51 paint = new Paint(Paint.ANTI_ALIAS_FLAG); /** 抗锯齿 */
52 paint.setColor(0xffffffff); /** a,r,g,b 255,255,255,255 */
53
54 random = new Random();
55 }
56
57 }
到这里总结一下,如果你想实现自己的自定义View,不妨直接继承 BaseView,然后写你自己的 Item,就可以了。
开源地址