(本博客为原创:http://www.cnblogs.com/linguanh/)
目录:
效果展示
感想
代码拆解
开源地址
效果展示
有没有兴趣继续看下去,直接看下"颜值"是第一步了。依次对应:下雨,飘雪,红包雨,碰撞球
上面是图片,这里再发个视频链接:http://pan.baidu.com/s/1miyPn76。
感想
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,就可以了。
开源地址
我的:https://github.com/af913337456/XView