展会信息港展会大全

[Android] SurfaceView使用实例
来源:互联网   发布日期:2015-10-03 15:17:58   浏览:1845次  

导读:同样,先上效果图如下:效果图中,抛物线的动画即是由SurfaceView实现的。底部栏中的文字翻转详情相关帖子:[Android] 文字翻转动画的实现需求:1.实现抛物线动画...

同样,先上效果图如下:

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

[Android] 文字翻转动画的实现

需求:

1.实现抛物线动画

1.1 设计物理模型,能够根据时间变量计算出某个时刻图片的X/Y坐标。

1.2 将图片高频率(相比于UI线程的缓慢而言)刷新到界面中。这儿需要实现将脏界面清屏及刷新操作。

2.文字翻转动画(已解决,见上面的帖子链接)

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

-----------------------------------------------------------------------------

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

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

可以有:图片要从高度为H的位置下落,并且第一次与X轴碰撞时会出现能量损失,至原来的N%。并且我们需要图片的最终落点离起始位置在X轴上的位移为L,默认存在重力加速度g。

详细的物理分析见上图啦,下面只说代码中如何实现,相关代码在PhysicalTool.java。

第一次下落过程所耗时t1与高度height会有如下关系:

[java]

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

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

[java]

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

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

[java]

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

则根据时间计算图片的实时坐标有:

PhysicalTool.comput()

[java]

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,该回调提供了三个方法:

[java]

surfaceCreated()//通知Surface已被创建,可以在此处启动动画线程

surfaceChanged()//通知Surface已改变

surfaceDestroyed()//通知Surface已被销毁,可以在此处终止动画线程

SurfaceView使用有一个原则,即该界面操作必须在surfaceCreated之后及surfaceDestroyed之前。该回调的监听通过SurfaceHolder设置。代码如下:

[java]

//于SurfaceView类中,该类实现SurfaceHolder.Callback接口,如本例中的ParabolaView

SurfaceHolder holder = getHolder();

holder.addCallback(this);

示例代码中,通过启动DrawThread调用handleThread()实现对SurfaceView的刷新。

刷新界面首先需要执行holder.lockCanvas()锁定Canvas并获得Canvas实例,然后进行界面更新操作,最后结束锁定Canvas,提交界面更改,至Surface最终显示在屏幕上。

代码如下:

[java]

canvas = holder.lockCanvas();

… … … …

… … … …

canvas.drawBitmap(bitmap, x, y, paint);

holder.unlockCanvasAndPost(canvas);

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

[java]

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

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

Android开发之SurfaceView

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

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

[java]

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;

}

}

}

[java]

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;

}

}

[java]

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);

}

}

[java]

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;

}

}

[java]

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);

}

}

作者:sodino

赞助本站

人工智能实验室

相关热词: android开发 教程

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港