[Android] SurfaceView使用实例
转自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;
- }
----------------------------------------------------------------------------------------
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接口,如本例中的ParabolaView
- 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;
- /** 购物车处显示购物数量的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.java
- package 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.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;
- /**
- * @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.java
- package 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.java
- package 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);
- }
- }
夜深了,晚安啰...