同樣,先上效果圖如下:
效果圖中,拋物線的動畫即是由SurfaceView實現的。底部欄中的文字翻轉詳情相關帖子:
Android開發教程:文字翻轉動畫的實現 http://www.linuxidc.com/Linux/2012-06/64051.htm
需求:
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接口,如本例中的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 http://www.linuxidc.com/Linux/2012-06/64052.htm
慣例,Java代碼如下,XML請自行實現
- 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:[email protected]
- * @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;
- }
- }