【Android简易动画】计时小球(TimeBall)随时间变化不断跳动的小球

来源:互联网 发布:java图形界面的布局 编辑:程序博客网 时间:2024/05/17 03:39

别的不说,先上效果图


图中的上下两行分别代表的是分钟的数字和秒钟的数字,这个东西是我一个前端的同学首先用JS在网页先实现了效果的,于是我去尝试在Android端实现这个东西,代码我已上传在csdn,欢迎大家一起讨论。  点击这里下载源代码



接下来该讲讲在这次实现功能的过程中遇到的一些问题。由于学到了比较多的东西,一些东西我就精简而过。


首先,要做这个东西,就得先想怎么去实现它,自定义View的实现首当其冲,由于从逻辑上来讲,每个小球都得是一个单独跳动的对象,所以,不得不为每一个小球实例化一个对象,这样一来,在写的时候也会容易有思路,因为就像单独判断一个小球的运动而已。因为要形成动画,所以必须每隔一段时间就重绘一次,这个时候就需要有一个计时操作了。

一开始我用的是在自定义View里面放Handler来实现耗时操作,这显然非常不科学,我就不讲了。。

第二次改,用了List存放每一个运动的小球,在Activity中进行计时操作来实现对控制每个小球运动。

Timer timer = new Timer();TimerTask timerTask = new TimerTask() {@Overridepublic void run() {for(Ball ball:list)ball.falling();}};

一开始每出现什么问题,但是到后来,运动的小球越来越多,资源却没有被回收,导致应用非常消耗资源,所以就想,必须加判断,如果小球不在屏幕内了,将它移出Layout,也移出List。这时候问题就来了,非UI线程中不能进行UI操作,无法从Layout中remove掉没有用的小球,那就想别的办法!于是又用回了Handler。


mHandler = new Handler() {public void handleMessage(android.os.Message msg) {if (msg.what == 1) {for (Ball ball : list) {if (ball.outOfScreen()) {//若小球已不在屏幕内,移出界面,并从list中移出layout.removeView(ball);list.remove(ball);} else  //否则继续下掉ball.falling();}sendEmptyMessageDelayed(1, 20);//延迟20毫秒再次发送消息}};};
可是这个时候问题又来了!List遍历过程不能中不能删除当前元素,否则会有java.util.ConcurrentModificationException异常,除了Iterator的remove()方法可以实现,一波三折,最终确定为

mHandler = new Handler() {public void handleMessage(android.os.Message msg) {if (msg.what == 1) {Ball ball;for (Iterator iterator = mMovingBall.iterator();iterator.hasNext();) {ball = (Ball) iterator.next();if (ball.outOfScreen()) {//若小球已不在屏幕内,移出界面,并从list中移出mContainer.removeView(ball);iterator.remove();} else//否则继续下掉ball.falling();}sendEmptyMessageDelayed(1, 20);//延迟20毫秒再次发送消息}};};

这里移出小球后,由于java的垃圾回收机制,内存总会被回收的(不保证什么时候回收)。


到这一步解决完了之后,小球的掉落动画明显变得很流畅了。
至于动画的完成,很明显就是调用invalidate()方法,并改变小球的圆心坐标,这时候快速重绘就能形成动画的效果,再判断小球是否触及屏幕底部,如果触及,将竖直方向的速度变为负即可,这样就完成了抛物线式的弹跳(期间可能要考虑“能量消耗”,所以适当减速也可,但是我没有做,因为往往看不出效果的时候,小球就已经跳出屏幕了)。


第一次对View重写,有一个东西也不是很理解,在重新调用invalidate()方法时,为什么上一次onDraw()画的东西不会还留在屏幕上,于是我觉得应该仔细看看invalidate()的实现。

查看了View的源代码,发现invalidate()实际上调用的是draw()方法,而在draw()方法中有以下注释

        /*         * Draw traversal performs several drawing steps which must be executed         * in the appropriate order:         *         *      1. Draw the background         *      2. If necessary, save the canvas' layers to prepare for fading         *      3. Draw view's content         *      4. Draw children         *      5. If necessary, draw the fading edges and restore layers         *      6. Draw decorations (scrollbars for instance)         */

onDraw过程中绘制的图形是在canvas即画布上的,而画布实质上指的是View的背景,draw()过程的第一步就重新绘制了background,所以原先画的图形并不会保留下来,到第三步中有

        // Step 3, draw the content        if (!dirtyOpaque) onDraw(canvas);
这时候再调用onDraw(),则后来再绘制的图形就覆盖在新的背景上了(若无属性设置背景,则背景为透明)。



0 0
原创粉丝点击