906
技術社區[雲棲]
[Android] SurfaceView使用實例
https://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所有,轉載請注明出處:https://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);
- }
- }
最後更新:2017-04-03 18:51:44