http://blog.csdn.net/sodino/article/details/7704084
同样,先上效果图如下:

效果图中,抛物线的动画即是由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()
-
surfaceChanged()
-
surfaceDestroyed()
SurfaceView使用有一个原则,即该界面操作必须在surfaceCreated之后及surfaceDestroyed之前。该回调的监听通过SurfaceHolder设置。代码如下:
-
-
SurfaceHolder 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.java
-
-
package 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;
-
-
private TextView txtNumber;
-
-
private int number;
-
private Handler handler;
-
-
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));
-
-
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) {
-
-
if (enableRefresh && interpolatedTime > 0.5f) {
-
txtNumber.setText(Integer.toString(number));
-
-
enableRefresh = false;
-
}
-
}
-
}
-
DrawThread.java
-
-
package lab.sodino.surfaceview;
-
-
import android.view.SurfaceView;
-
-
-
-
-
-
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.java
-
package 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;
-
-
-
-
-
-
public class ParabolaView extends SurfaceView implements SurfaceHolder.Callback {
-
-
private static final long SLEEP_DURATION = 10l;
-
private SurfaceHolder holder;
-
-
private Bitmap bitmap;
-
private DrawThread thread;
-
private PhysicalTool physicalTool;
-
private ParabolaView.ParabolaListener listener;
-
-
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);
-
-
-
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.getMirrorY(getHeight(), bitmap.getHeight());
-
-
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();
-
}
-
}
-
-
-
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();
-
}
-
}
-
-
-
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.java
-
package lab.sodino.surfaceview;
-
-
-
-
-
-
public class PhysicalTool {
-
-
private static final float GRAVITY = 400.78033f;
-
-
private static final float WASTAGE = 0.3f;
-
-
private int height;
-
-
private int width;
-
-
private double velocity;
-
-
private double x, y;
-
-
private long startTime;
-
-
private double t1;
-
-
private double t2;
-
-
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);
-
}
-
-
-
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;
-
}
-
-
-
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.java
-
package lab.sodino.surfaceview;
-
-
import android.graphics.Camera;
-
import android.graphics.Matrix;
-
import android.view.animation.Animation;
-
import android.view.animation.Transformation;
-
-
-
-
-
-
public class RotateAnimation extends Animation {
-
-
public static final boolean DEBUG = false;
-
-
public static final boolean ROTATE_DECREASE = true;
-
-
public static final boolean ROTATE_INCREASE = false;
-
-
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;
-
-
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) {
-
-
super.initialize(width, height, parentWidth, parentHeight);
-
camera = new Camera();
-
}
-
-
public void setInterpolatedTimeListener(InterpolatedTimeListener listener) {
-
this.listener = listener;
-
}
-
-
protected void applyTransformation(float interpolatedTime, Transformation transformation) {
-
-
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) {
-
-
degree = degree - 180;
-
}
-
-
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);
-
}
-
}