同样,先上效果图如下:


效果图中,抛物线的动画即是由SurfaceView实现的。底部栏中的文字翻转详情相关帖子:
[Android] 文字翻转动画的实现

需求:
1.实现抛物线动画
1.1 设计物理模型,能够根据时间变量计算出某个时刻图片的X/Y坐标。
1.2 将图片高频率(相比于UI线程的缓慢而言)刷新到界面中。这儿需要实现将脏界面清屏及刷新操作。
2.文字翻转动画(已解决,见上面的帖子链接)

下面来逐一解决所提出的问题。

-----------------------------------------------------------------------------
分隔线内容与Android无关,请慎读,勿拍砖。谢啦



1.1 设计物理模型,如果大家还记得初中物理时,这并不难。自己写的草稿图见下:


可以有:图片要从高度为H的位置下落,并且第一次与X轴碰撞时会出现能量损失,至原来的N%。并且我们需要图片的最终落点离起始位置在X轴上的位移为L,默认存在重力加速度g。
详细的物理分析见上图啦,下面只说代码中如何实现,相关代码在PhysicalTool.java。
第一次下落过程所耗时t1与高度height会有如下关系:

t1 = Math.sqrt(2 * height * 1.0d / GRAVITY);

第一次与X轴碰撞后上升至最高点的耗时t2与高度 N%*height会有:

t2 = Math.sqrt((1 - WASTAGE) * 2 * height * 1.0d / GRAVITY);

那么总的动画时间为(t1 + t2 + t2),则水平位移速度有(width为X轴总位移):

velocity = width * 1.0d / (t1 + 2 * t2);

则根据时间计算图片的实时坐标有:
PhysicalTool.comput()

                double used = (System.currentTimeMillis() - startTime) * 1.0d / 1000;                x = velocity * used;                if (0 <= used && used < t1) {                        y = height - 0.5d * GRAVITY * used * used;                } else if (t1 <= used && used < (t1 + t2)) {                        double tmp = t1 + t2 - used;                        y = (1 - WASTAGE) * height - 0.5d * GRAVITY * tmp * tmp;                } else if ((t1 + t2) <= used && used < (t1 + 2 * t2)) {                        double tmp = used - t1 - t2;                        y = (1 - WASTAGE) * height - 0.5d * GRAVITY * tmp * tmp;                }
Android无关内容结束了。
----------------------------------------------------------------------------------------

1.2 SurfaceView刷新界面
SurfaceView是一个特殊的UI组件,特殊在于它能够使用非UI线程刷新界面。至于为何具有此特殊性,将在另一个帖子"SurfaceView 相关知识笔记"中讨论,该帖子将讲述SurfaceView、Surface、ViewRoot、Window Manager/Window、Canvas等之间的关系。

使用SurfaceView需要自定义组件继承该类,并实现SurfaceHolder.Callback,该回调提供了三个方法:

surfaceCreated()//通知Surface已被创建,可以在此处启动动画线程surfaceChanged()//通知Surface已改变surfaceDestroyed()//通知Surface已被销毁,可以在此处终止动画线程

SurfaceView使用有一个原则,即该界面操作必须在surfaceCreated之后及surfaceDestroyed之前。该回调的监听通过SurfaceHolder设置。代码如下:
//于SurfaceView类中,该类实现SurfaceHolder.Callback接口,如本例中的ParabolaViewSurfaceHolder holder = getHolder();holder.addCallback(this);

示例代码中,通过启动DrawThread调用handleThread()实现对SurfaceView的刷新。
刷新界面首先需要执行holder.lockCanvas()锁定Canvas并获得Canvas实例,然后进行界面更新操作,最后结束锁定Canvas,提交界面更改,至Surface最终显示在屏幕上。
代码如下:

                                canvas = holder.lockCanvas();                                … … … …                                 … … … …                                 canvas.drawBitmap(bitmap, x, y, paint);                                holder.unlockCanvasAndPost(canvas);


本例中,需要清除屏幕脏区域,出于简便的做法,是将整个SurfaceView背景重复地设置为透明,代码为:

canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);

对于SurfaceView的操作,下面这个链接讲述得更详细,更易理解,推荐去看下:
Android开发之SurfaceView

惯例,Java代码如下,XML请自行实现

本文由Sodino所有,转载请注明出处:http://blog.csdn.net/sodino/article/details/7704084

ActSurfaceView.javapackage lab.sodino.surfaceview;import lab.sodino.surfaceview.RotateAnimation.InterpolatedTimeListener;import android.app.Activity;import android.graphics.BitmapFactory;import android.os.Bundle;import android.os.Handler;import android.os.Handler.Callback;import android.os.Message;import android.view.View;import android.view.View.OnClickListener;import android.view.ViewGroup;import android.widget.Button;import android.widget.TextView;public class ActSurfaceView extends Activity implements OnClickListener, ParabolaView.ParabolaListener, Callback,                InterpolatedTimeListener {        public static final int REFRESH_TEXTVIEW = 1;        private Button btnStartAnimation;        /** 动画界面。 */        private ParabolaView parabolaView;        /** 购物车处显示购物数量的TextView。 */        private TextView txtNumber;        /** 购物车中的数量。 */        private int number;        private Handler handler;        /** TextNumber是否允许显示最新的数字。 */        private boolean enableRefresh;        public void onCreate(Bundle savedInstanceState) {                super.onCreate(savedInstanceState);                setContentView(R.layout.main);                handler = new Handler(this);                number = 0;                btnStartAnimation = (Button) findViewById(R.id.btnStartAnim);                btnStartAnimation.setOnClickListener(this);                parabolaView = (ParabolaView) findViewById(R.id.surfaceView);                parabolaView.setParabolaListener(this);                txtNumber = (TextView) findViewById(R.id.txtNumber);        }        public void onClick(View v) {                if (v == btnStartAnimation) {                        LogOut.out(this, "isShowMovie:" + parabolaView.isShowMovie());                        if (parabolaView.isShowMovie() == false) {                                number++;                                enableRefresh = true;                                parabolaView.setIcon(BitmapFactory.decodeResource(getResources(), R.drawable.icon));                                // 设置起始Y轴高度和终止X轴位移                                parabolaView.setParams(200, ((ViewGroup) txtNumber.getParent()).getLeft());                                parabolaView.showMovie();                        }                }        }        public void onParabolaStart(ParabolaView view) {        }        public void onParabolaEnd(ParabolaView view) {                handler.sendEmptyMessage(REFRESH_TEXTVIEW);        }        public boolean handleMessage(Message msg) {                switch (msg.what) {                case REFRESH_TEXTVIEW:                        if (txtNumber.getVisibility() != View.VISIBLE) {                                txtNumber.setVisibility(View.VISIBLE);                        }                        RotateAnimation anim = new RotateAnimation(txtNumber.getWidth() >> 1, txtNumber.getHeight() >> 1,                                        RotateAnimation.ROTATE_INCREASE);                        anim.setInterpolatedTimeListener(this);                        txtNumber.startAnimation(anim);                        break;                }                return false;        }        @Override        public void interpolatedTime(float interpolatedTime) {                // 监听到翻转进度过半时,更新txtNumber显示内容。                if (enableRefresh && interpolatedTime > 0.5f) {                        txtNumber.setText(Integer.toString(number));                        // Log.d("ANDROID_LAB", "setNumber:" + number);                        enableRefresh = false;                }        }}

DrawThread.javapackage lab.sodino.surfaceview;import android.view.SurfaceView;/** * @author Sodino E-mail:sodinoopen@hotmail.com * @version Time:2012-6-18 上午03:14:31 */public class DrawThread extends Thread {        private SurfaceView surfaceView;        private boolean running;        public DrawThread(SurfaceView surfaceView) {                this.surfaceView = surfaceView;        }        public void run() {                if (surfaceView == null) {                        return;                }                if (surfaceView instanceof ParabolaView) {                        ((ParabolaView) surfaceView).handleThread();                }        }        public void setRunning(boolean b) {                running = b;        }        public boolean isRunning() {                return running;        }}

ParabolaView.javapackage lab.sodino.surfaceview;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PixelFormat;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;/** * @author Sodino E-mail:sodinoopen@hotmail.com * @version Time:2012-6-18 上午02:52:33 */public class ParabolaView extends SurfaceView implements SurfaceHolder.Callback {        /** 每30ms刷一帧。 */        private static final long SLEEP_DURATION = 10l;        private SurfaceHolder holder;        /** 动画图标。 */        private Bitmap bitmap;        private DrawThread thread;        private PhysicalTool physicalTool;        private ParabolaView.ParabolaListener listener;        /** 默认未创建,相当于Destory。 */        private boolean surfaceDestoryed = true;        public ParabolaView(Context context, AttributeSet attrs, int defStyle) {                super(context, attrs, defStyle);                init();        }        public ParabolaView(Context context, AttributeSet attrs) {                super(context, attrs);                init();        }        public ParabolaView(Context context) {                super(context);                init();        }        private void init() {                holder = getHolder();                holder.addCallback(this);                holder.setFormat(PixelFormat.TRANSPARENT);                setZOrderOnTop(true);                // setZOrderOnTop(false);                physicalTool = new PhysicalTool();        }        @Override        public void surfaceCreated(SurfaceHolder holder) {                surfaceDestoryed = false;        }        @Override        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {        }        @Override        public void surfaceDestroyed(SurfaceHolder holder) {                LogOut.out(this, "surfaceDestroyed");                surfaceDestoryed = true;                physicalTool.cancel();        }        public void handleThread() {                Canvas canvas = null;                Paint pTmp = new Paint();                pTmp.setAntiAlias(true);                pTmp.setColor(Color.RED);                Paint paint = new Paint();                // 设置抗锯齿                paint.setAntiAlias(true);                paint.setColor(Color.CYAN);                physicalTool.start();                LogOut.out(this, "doing:" + physicalTool.doing());                if (listener != null) {                        listener.onParabolaStart(this);                }                while (physicalTool.doing()) {                        try {                                physicalTool.compute();                                canvas = holder.lockCanvas();                                // 设置画布的背景为透明。                                canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);                                // 绘上新图区域                                float x = (float) physicalTool.getX();                                // float y = (float) physicalTool.getY();                                float y = (float) physicalTool.getMirrorY(getHeight(), bitmap.getHeight());                                // LogOut.out(this, "x:" + x + " y:" + y);                                canvas.drawRect(x, y, x + bitmap.getWidth(), y + bitmap.getHeight(), pTmp);                                canvas.drawBitmap(bitmap, x, y, paint);                                holder.unlockCanvasAndPost(canvas);                                Thread.sleep(SLEEP_DURATION);                        } catch (Exception e) {                                e.printStackTrace();                        }                }                // 清除屏幕内容                // 直接按"Home"回桌面,SurfaceView被销毁了,lockCanvas返回为null。                if (surfaceDestoryed == false) {                        canvas = holder.lockCanvas();                        canvas.drawColor(Color.TRANSPARENT, android.graphics.PorterDuff.Mode.CLEAR);                        holder.unlockCanvasAndPost(canvas);                }                thread.setRunning(false);                if (listener != null) {                        listener.onParabolaEnd(this);                }        }        public void showMovie() {                if (thread == null) {                        thread = new DrawThread(this);                } else if (thread.getState() == Thread.State.TERMINATED) {                        thread.setRunning(false);                        thread = new DrawThread(this);                }                LogOut.out(this, "thread.getState:" + thread.getState());                if (thread.getState() == Thread.State.NEW) {                        thread.start();                }        }        /** 正在播放动画时,返回true;否则返回false。 */        public boolean isShowMovie() {                return physicalTool.doing();        }        public void setIcon(Bitmap bit) {                bitmap = bit;        }        public void setParams(int height, int width) {                physicalTool.setParams(height, width);        }        /** 设置抛物线的动画监听器。 */        public void setParabolaListener(ParabolaView.ParabolaListener listener) {                this.listener = listener;        }        static interface ParabolaListener {                public void onParabolaStart(ParabolaView view);                public void onParabolaEnd(ParabolaView view);        }}


PhysicalTool.javapackage lab.sodino.surfaceview;/** * @author Sodino E-mail:sodinoopen@hotmail.com * @version Time:2012-6-18 上午06:07:16 */public class PhysicalTool {        /** 重力加速度值。 */        private static final float GRAVITY = 400.78033f;        /** 与X轴碰撞后,重力势能损失掉的百分比。 */        private static final float WASTAGE = 0.3f;        /** 起始下降高度。 */        private int height;        /** 起始点到终点的X轴位移。 */        private int width;        /** 水平位移速度。 */        private double velocity;        /** X Y坐标。 */        private double x, y;        /** 动画开始时间。 */        private long startTime;        /** 首阶段下载的时间。 单位:毫秒。 */        private double t1;        /** 第二阶段上升与下载的时间。 单位:毫秒。 */        private double t2;        /** 动画正在进行时值为true,反之为false。 */        private boolean doing;        public void start() {                startTime = System.currentTimeMillis();                doing = true;        }        /** 设置起始下落的高度及水平初速度;并以此计算小球下落的第一阶段及第二阶段上升耗时。 */        public void setParams(int h, int w) {                height = h;                width = w;                t1 = Math.sqrt(2 * height * 1.0d / GRAVITY);                t2 = Math.sqrt((1 - WASTAGE) * 2 * height * 1.0d / GRAVITY);                velocity = width * 1.0d / (t1 + 2 * t2);                LogOut.out(this, "t1=" + t1 + " t2=" + t2);        }        /** 根据当前时间计算小球的X/Y坐标。 */        public void compute() {                double used = (System.currentTimeMillis() - startTime) * 1.0d / 1000;                x = velocity * used;                if (0 <= used && used < t1) {                        y = height - 0.5d * GRAVITY * used * used;                } else if (t1 <= used && used < (t1 + t2)) {                        double tmp = t1 + t2 - used;                        y = (1 - WASTAGE) * height - 0.5d * GRAVITY * tmp * tmp;                } else if ((t1 + t2) <= used && used < (t1 + 2 * t2)) {                        double tmp = used - t1 - t2;                        y = (1 - WASTAGE) * height - 0.5d * GRAVITY * tmp * tmp;                } else {                        LogOut.out(this, "used:" + used + " set doing false");                        x = velocity * (t1 + 2 * t2);                        y = 0;                        doing = false;                }        }        public double getX() {                return x;        }        public double getY() {                return y;        }        /** 反转Y轴正方向。适应手机的真实坐标系。 */        public double getMirrorY(int parentHeight, int bitHeight) {                int half = parentHeight >> 1;                double tmp = half + (half - y);                tmp -= bitHeight;                return tmp;        }        public boolean doing() {                return doing;        }        public void cancel() {                doing = false;        }}

RotateAnimation.javapackage lab.sodino.surfaceview;import android.graphics.Camera;import android.graphics.Matrix;import android.view.animation.Animation;import android.view.animation.Transformation;/** * @author Sodino E-mail:sodinoopen@hotmail.com * @version Time:2012-6-27 上午07:32:00 */public class RotateAnimation extends Animation {        /** 值为true时可明确查看动画的旋转方向。 */        public static final boolean DEBUG = false;        /** 沿Y轴正方向看,数值减1时动画逆时针旋转。 */        public static final boolean ROTATE_DECREASE = true;        /** 沿Y轴正方向看,数值减1时动画顺时针旋转。 */        public static final boolean ROTATE_INCREASE = false;        /** Z轴上最大深度。 */        public static final float DEPTH_Z = 310.0f;        /** 动画显示时长。 */        public static final long DURATION = 800l;        /** 图片翻转类型。 */        private final boolean type;        private final float centerX;        private final float centerY;        private Camera camera;        /** 用于监听动画进度。当值过半时需更新txtNumber的内容。 */        private InterpolatedTimeListener listener;        public RotateAnimation(float cX, float cY, boolean type) {                centerX = cX;                centerY = cY;                this.type = type;                setDuration(DURATION);        }        public void initialize(int width, int height, int parentWidth, int parentHeight) {                // 在构造函数之后、getTransformation()之前调用本方法。                super.initialize(width, height, parentWidth, parentHeight);                camera = new Camera();        }        public void setInterpolatedTimeListener(InterpolatedTimeListener listener) {                this.listener = listener;        }        protected void applyTransformation(float interpolatedTime, Transformation transformation) {                // interpolatedTime:动画进度值,范围为[0.0f,10.f]                if (listener != null) {                        listener.interpolatedTime(interpolatedTime);                }                float from = 0.0f, to = 0.0f;                if (type == ROTATE_DECREASE) {                        from = 0.0f;                        to = 180.0f;                } else if (type == ROTATE_INCREASE) {                        from = 360.0f;                        to = 180.0f;                }                float degree = from + (to - from) * interpolatedTime;                boolean overHalf = (interpolatedTime > 0.5f);                if (overHalf) {                        // 翻转过半的情况下,为保证数字仍为可读的文字而非镜面效果的文字,需翻转180度。                        degree = degree - 180;                }                // float depth = 0.0f;                float depth = (0.5f - Math.abs(interpolatedTime - 0.5f)) * DEPTH_Z;                final Matrix matrix = transformation.getMatrix();                camera.save();                camera.translate(0.0f, 0.0f, depth);                camera.rotateY(degree);                camera.getMatrix(matrix);                camera.restore();                if (DEBUG) {                        if (overHalf) {                                matrix.preTranslate(-centerX * 2, -centerY);                                matrix.postTranslate(centerX * 2, centerY);                        }                } else {                        matrix.preTranslate(-centerX, -centerY);                        matrix.postTranslate(centerX, centerY);                }        }        /** 动画进度监听器。 */        public static interface InterpolatedTimeListener {                public void interpolatedTime(float interpolatedTime);        }}

夜深了,晚安啰...

更多相关文章

  1. Android中的动画详析-kotlin的demo
  2. android自定义动画的一点感悟
  3. Android(安卓)中的“后台无效动画“行为分析
  4. Android中如何使用rotate实现图片不停旋转的效果与动画的停止
  5. Android(安卓)高级UI解密 (五) :PathMeasure截取片段 与 切线(新思
  6. Android属性动画完全解析(下) Interpolator和ViewPropertyAnimat
  7. Android中的动画实现
  8. [置顶] Android(安卓)实现书籍翻页效果----升级篇
  9. Android(安卓)贝塞尔曲线简单应用(一)

随机推荐

  1. SQLite多线程操作数据库
  2. android repo 切换分支
  3. android 之SharedPreference
  4. Instant Run requires 'Tools | Android
  5. android开发中adb的用法
  6. 轉載 :【转】android UI 相关常用类简介
  7. Android 监听系统虚拟导航栏按键
  8. android 使用动画实例[1]
  9. Android的多种数据存储方式
  10. android中sqlite数据库升级方案