1. 程式人生 > >Android中動畫的詳細講解

Android中動畫的詳細講解

這裡寫圖片描述逐幀動畫:
這裡寫圖片描述補間動畫
這裡寫圖片描述結合動畫
這裡寫圖片描述自定義動畫
這裡寫圖片描述屬性動畫一
這裡寫圖片描述屬性動畫二
這裡寫圖片描述動畫三
這裡寫圖片描述動畫四

逐幀動畫

定義逐幀動畫,只要在<animation-list……>;元素中使用<item…/>子元素定義動畫的全部幀就可以了!

語法格式:

<?xml version="1.0" encoding="utf-8"?>
<!-- 指定動畫迴圈播放 -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot
="false">
<!-- 新增多個幀 --> <item android:drawable="圖片" android:duration="60" /> </animation-list >
MainActivity程式碼:
public class MainActivity extends Activity
{
    private MyView myView;
    private AnimationDrawable anim;
    private MediaPlayer bomb;
    public void
onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); // 使用FrameLayout佈局管理器,它允許元件自己控制位置 FrameLayout frame = new
FrameLayout(this); setContentView(frame); // 設定使用背景 frame.setBackgroundResource(R.drawable.back); // 載入音效 bomb = MediaPlayer.create(this, R.raw.bomb); myView = new MyView(this); // 設定myView用於顯示blast動畫 myView.setBackgroundResource(R.anim.blast); // 設定myView預設為隱藏 myView.setVisibility(View.INVISIBLE); // 獲取動畫物件 anim = (AnimationDrawable) myView.getBackground(); frame.addView(myView); frame.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View source, MotionEvent event) { // 只處理按下事件(避免每次產生兩個動畫效果) if (event.getAction() == MotionEvent.ACTION_DOWN) { // 先停止動畫播放 anim.stop(); float x = event.getX(); float y = event.getY(); // 控制myView的顯示位置 myView.setLocation((int) y - 40, (int) x - 20); myView.setVisibility(View.VISIBLE); // 啟動動畫 anim.start(); // 播放音效 bomb.start(); } return false; } }); } // 定義一個自定義View,該自定義View用於播放“爆炸”效果 class MyView extends ImageView { public MyView(Context context) { super(context); } // 定義一個方法,該方法用於控制MyView的顯示位置 public void setLocation(int top, int left) { this.setFrame(left-100, top-100, left + 100, top + 100); } // 重寫該方法,控制如果動畫播放到最後一幀時,隱藏該View @Override protected void onDraw(Canvas canvas) // ① { try { Field field = AnimationDrawable.class .getDeclaredField("mCurFrame"); field.setAccessible(true); // 獲取anim動畫的當前幀 int curFrame = field.getInt(anim); // 如果已經到了最後一幀 if (curFrame == anim.getNumberOfFrames() - 1) { // 讓該View隱藏 setVisibility(View.INVISIBLE); } } catch (Exception e) { } super.onDraw(canvas); } } }

補間動畫(Tween)

資源定義完成後,可以使用AnimationUtils工具類載入指定的動畫資源。

Animation子類:

  1. AlphaAnimation:
  2. ScaleAnimation:
  3. TranslateAnimation:
  4. RotateAnimation:

interpolator簡單理解

interpolator:根據特定的演算法計算出整個動畫所需要動態插入幀的密度和位置。負責控制動畫的變化速度。使其動畫效果更流暢。

程式碼
public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final ImageView flower = (ImageView)
                findViewById(R.id.flower);
        // 載入第一份動畫資源
        final Animation anim = AnimationUtils
                .loadAnimation(this, R.anim.anim);
        // 設定動畫結束後保留結束狀態
        anim.setFillAfter(true);
        // 載入第二份動畫資源
        final Animation reverse = AnimationUtils.loadAnimation(this
                , R.anim.reverse);
        // 設定動畫結束後保留結束狀態
        reverse.setFillAfter(true);
        Button bn = (Button) findViewById(R.id.bn);
        final Handler handler = new Handler()
        {
            @Override
            public void handleMessage(Message msg)
            {
                if (msg.what == 0x123)
                {
                    flower.startAnimation(reverse);
                }
            }
        };
        bn.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View arg0)
            {
                flower.startAnimation(anim);
                // 設定3.5秒後啟動第二個動畫
                new Timer().schedule(new TimerTask()
                {
                    @Override
                    public void run()
                    {
                        handler.sendEmptyMessage(0x123);
                    }
                }, 3500);
            }
        });
    }
}

結合動畫

MainActivity程式碼:
public class MainActivity extends Activity
{
    // 記錄蝴蝶ImageView當前的位置
    private float curX = 0;
    private float curY = 30;
    // 記錄蝴蝶ImageView下一個位置的座標
    float nextX = 0;
    float nextY = 0;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 獲取顯示蝴蝶的ImageView元件
        final ImageView imageView = (ImageView)
                findViewById(R.id.butterfly);
        final Handler handler = new Handler()
        {
            @Override
            public void handleMessage(Message msg)
            {
                if (msg.what == 0x123)
                {
                    // 橫向上一直向右飛
                    if (nextX > 320)
                    {
                        curX = nextX = 0;
                    }
                    else
                    {
                        nextX += 8;
                    }
                    // 縱向上可以隨機上下
                    nextY = curY + (float) (Math.random() * 10 - 5);
                    // 設定顯示蝴蝶的ImageView發生位移改變
                    TranslateAnimation anim = new TranslateAnimation(
                            curX, nextX, curY, nextY);
                    curX = nextX;
                    curY = nextY;
                    anim.setDuration(200);
                    // 開始位移動畫
                    imageView.startAnimation(anim); // ①
                }
            }
        };
        final AnimationDrawable butterfly = (AnimationDrawable)
                imageView.getBackground();
        imageView.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                // 開始播放蝴蝶振翅的逐幀動畫
                butterfly.start();  // ②
                // 通過定製器控制每0.2秒執行一
                // 次TranslateAnimation動畫
                new Timer().schedule(new TimerTask()
                {
                    @Override
                    public void run()
                    {
                        handler.sendEmptyMessage(0x123);
                    }
                }, 0, 200);
            }
        });
    }
}

自定義補間動畫:

自定義補間動畫需要繼承Animation,重寫抽象方法applyTransformation(float interpolatedTime,Transformation t)方法,引數說明:

  1. interpolatedTime:代表動畫的時間進行比,無論動畫實際的持續時間如何,當動畫播放時,該引數總是從0變化到1
  2. Transformation:代表補間動畫在不同時刻對圖形或者元件的變形程度

Camera常用方法如下:

  1. getMatrix(Matrix matrix):將Camera所做的變換應用到指定matrix上。

  2. rotateX(float deg):使目標元件沿X軸旋轉。

  3. rotateY(float deg):使目標元件沿Y軸旋轉。

  4. rotateZ(float deg):使目標元件沿Z軸旋轉。

  5. translate(float x,float y,float z):使目標元件在三維空間裡進行位移變換

  6. applyToCanvas(Canvas canvas):把Camera所做的變換應用到Canvas上。

MyAnimation程式碼:
public class MyAnimation extends Animation
{
    private float centerX;
    private float centerY;
    // 定義動畫的持續事件
    private int duration;
    private Camera camera = new Camera();
    public MyAnimation(float x, float y, int duration)
    {
        this.centerX = x;
        this.centerY = y;
        this.duration = duration;
    }
    @Override
    public void initialize(int width, int height
            , int parentWidth, int parentHeight)
    {
        super.initialize(width, height, parentWidth, parentHeight);
        // 設定動畫的持續時間
        setDuration(duration);
        // 設定動畫結束後效果保留
        setFillAfter(true);
        setInterpolator(new LinearInterpolator());
    }
    /*
     * 該方法的interpolatedTime代表了抽象的動畫持續時間,不管動畫實際持續時間多長,
     * interpolatedTime引數總是從0(動畫開始時)~1(動畫結束時)
     * Transformation引數代表了對目標元件所做的改變.
     */
    @Override
    protected void applyTransformation(float interpolatedTime
            , Transformation t)
    {
        camera.save();
        // 根據interpolatedTime時間來控制X、Y、Z上的偏移
        camera.translate(100.0f - 100.0f * interpolatedTime,
                150.0f * interpolatedTime - 150,
                80.0f - 80.0f * interpolatedTime);
        // 設定根據interpolatedTime時間在Y軸上旋轉不同角度
        camera.rotateY(360 * (interpolatedTime));
        // 設定根據interpolatedTime時間在X軸上旋轉不同角度
        camera.rotateX((360 * interpolatedTime));
        // 獲取Transformation引數的Matrix物件
        Matrix matrix = t.getMatrix();
        camera.getMatrix(matrix);
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
        camera.restore();
    }
}
MainActivity程式碼:
public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 獲取ListView元件
        ListView list = (ListView) findViewById(R.id.list);
        WindowManager windowManager = (WindowManager)
                getSystemService(WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrice = new DisplayMetrics();
        // 獲取螢幕的寬和高
        display.getMetrics(metrice);
        // 設定對ListView元件應用動畫
        list.setAnimation(new MyAnimation(metrice.xdpi / 2
                , metrice.ydpi / 2, 3500));
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
<ListView  
    android:id="@+id/list"
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:entries="@array/bookArray"
    />
</LinearLayout>

屬性動畫

定義屬性動畫的方法:

  1. 使用ValurAnimator或者ObjectAnimator的靜態工廠方法來建立動畫
  2. 使用資原始檔來定義動畫

使用屬性動畫的步驟

  1. 建立ValurAniamtor或ObjectAnimator物件,即可從XML資源載入,也可以使用ValurAniamtor或ObjectAnimator的靜態工廠方法建立
  2. 根據需要設定Animator屬性值
  3. 如果需要監聽Animator的改變事件,需要為Animator設定事件監聽器
  4. 如果有多個動畫需要按次序或者同時處理,則需要使用AnimatorSet組合這些動畫
  5. 呼叫Animator的start()方法來啟動。

    屬性動畫和補間動畫的區別:

  6. 補間動畫只能定義兩個關鍵幀在“透明度
    ”“旋轉”“縮放”“位移”4個方面變化。屬性動畫可以定義任何屬性的變化

  7. 補間動畫只對UI元件執行動畫,但屬性動畫幾乎可以對任何物件執行動畫,不管是否顯示在螢幕上

屬性動畫的API:

  1. Animator:它提供建立屬性動畫的基類,通常用於被繼承並重寫它的相關方法
  2. ValueAnimator:用於計算相關屬性值
  3. ObjectAnimator:ValueAnimator的子類,允許程式設計師對指定物件的屬性執行動畫。
  4. AnimatorSet:Animator的子類,用於組合多個Animator,並指定多個Animator按次序播放還是同時播放。
  5. IntEvaluator:用於計算int型別的計算器
  6. FloatEvaluator:用於計算float型別屬性值的計算器
  7. ArgbEvaluator:用於計算以十六進位制形式表示的顏色值的計算器
  8. TypeEvaluator:計算器介面,開發者可以通過實現該介面來實現自定義計算器。

    屬性動畫一

    ShapeHolder程式碼:
public class ShapeHolder
{
    private float x = 0, y = 0;
    private ShapeDrawable shape;
    private int color;
    private RadialGradient gradient;
    private float alpha = 1f;
    private Paint paint;

    public ShapeHolder(ShapeDrawable s)
    {
        shape = s;
    }

    public float getX()
    {
        return x;
    }

    public void setX(float x)
    {
        this.x = x;
    }

    public float getY()
    {
        return y;
    }

    public void setY(float y)
    {
        this.y = y;
    }

    public ShapeDrawable getShape()
    {
        return shape;
    }

    public void setShape(ShapeDrawable shape)
    {
        this.shape = shape;
    }

    public int getColor()
    {
        return color;
    }

    public void setColor(int color)
    {
        this.color = color;
    }

    public RadialGradient getGradient()
    {
        return gradient;
    }

    public void setGradient(RadialGradient gradient)
    {
        this.gradient = gradient;
    }

    public float getAlpha()
    {
        return alpha;
    }

    public void setAlpha(float alpha)
    {
        this.alpha = alpha;
    }

    public Paint getPaint()
    {
        return paint;
    }

    public void setPaint(Paint paint)
    {
        this.paint = paint;
    }
}
MainActivity程式碼;
public class MainActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 獲取ListView元件
        ListView list = (ListView) findViewById(R.id.list);
        WindowManager windowManager = (WindowManager)
                getSystemService(WINDOW_SERVICE);
        Display display = windowManager.getDefaultDisplay();
        DisplayMetrics metrice = new DisplayMetrics();
        // 獲取螢幕的寬和高
        display.getMetrics(metrice);
        // 設定對ListView元件應用動畫
        list.setAnimation(new MyAnimation(metrice.xdpi / 2
                , metrice.ydpi / 2, 3500));
    }
}

屬性動畫二:

ShapeHolder程式碼:

同屬性動畫一中的ShapeHolder程式碼:

MainActivity程式碼:
public class MainActivity extends Activity
{
    // 定義小球的大小的常量
    static final float BALL_SIZE = 50F;
    // 定義小球從螢幕上方下落到螢幕底端的總時間
    static final float FULL_TIME = 3000;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        LinearLayout container = (LinearLayout)
            findViewById(R.id.container);
        // 設定該視窗顯示MyAnimationView元件
        container.addView(new MyAnimationView(this));
    }
    public class MyAnimationView extends View
    {
        public final ArrayList<ShapeHolder> balls
                = new ArrayList<ShapeHolder>();
        public MyAnimationView(Context context)
        {
            super(context);
            // 載入動畫資源
            ObjectAnimator colorAnim = (ObjectAnimator) AnimatorInflater
                .loadAnimator(MainActivity.this, R.animator.color_anim);
            colorAnim.setEvaluator(new ArgbEvaluator());
            // 對該View本身應用屬性動畫
            colorAnim.setTarget(this);
            // 開始指定動畫
            colorAnim.start();
        }
        @Override
        public boolean onTouchEvent(MotionEvent event)
        {
            // 如果觸碰事件不是按下、移動事件
            if (event.getAction() != MotionEvent.ACTION_DOWN
                && event.getAction() != MotionEvent.ACTION_MOVE)
            {
                return false;
            }
            //  在事件發生點新增一個小球(用一個圓形代表)
            ShapeHolder newBall = addBall(event.getX(), event.getY());
            // 計算小球下落動畫開始時的y座標
            float startY = newBall.getY();
            // 計算小球下落動畫結束時的y座標(落到螢幕最下方,就是螢幕高度減去小球高度)
            float endY = getHeight() - BALL_SIZE;
            // 獲取螢幕高度
            float h = (float) getHeight();
            float eventY = event.getY();
            // 計算動畫的持續時間
            int duration = (int) (FULL_TIME * ((h - eventY) / h));
            // 定義小球“落下”的動畫:
            // 讓newBall物件的y屬性從事件發生點變化到螢幕最下方
            ValueAnimator fallAnim = ObjectAnimator.ofFloat(
                newBall, "y", startY, endY);
            // 設定fallAnim動畫的持續時間
            fallAnim.setDuration(duration);
            // 設定fallAnim動畫的插值方式:加速插值
            fallAnim.setInterpolator(new AccelerateInterpolator());
            // 定義小球“壓扁”的動畫:該動畫控制小球的x座標“向左移”半個球
            ValueAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall
                    , "x", newBall.getX(), newBall.getX() - BALL_SIZE / 2);
            // 設定squashAnim1動畫持續時間
            squashAnim1.setDuration(duration / 4);
            // 設定squashAnim1動畫重複1次
            squashAnim1.setRepeatCount(1);
            // 設定squashAnim1動畫的重複方式
            squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
            // 設定squashAnim1動畫的插值方式:減速插值
            squashAnim1.setInterpolator(new DecelerateInterpolator());
            // 定義小球“壓扁”的動畫:該動畫控制小球的寬度加倍
            ValueAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall,
                    "width", newBall.getWidth()
                    , newBall.getWidth() + BALL_SIZE);
            // 設定squashAnim2動畫持續時間
            squashAnim2.setDuration(duration / 4);
            // 設定squashAnim2動畫重複1次
            squashAnim2.setRepeatCount(1);
            // 設定squashAnim2動畫的重複方式
            squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
            // 設定squashAnim2動畫的插值方式:減速插值
            squashAnim2.setInterpolator(new DecelerateInterpolator());
            // 定義小球“拉伸”的動畫:該動畫控制小球的y座標“向下移”半個球
            ObjectAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall
                    , "y", endY, endY + BALL_SIZE / 2);
            // 設定stretchAnim1動畫持續時間
            stretchAnim1.setDuration(duration / 4);
            // 設定stretchAnim1動畫重複1次
            stretchAnim1.setRepeatCount(1);
            // 設定stretchAnim1動畫的重複方式
            stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
            // 設定stretchAnim1動畫的插值方式:減速插值
            stretchAnim1.setInterpolator(new DecelerateInterpolator());
            // 定義小球“拉伸”的動畫:該動畫控制小球的高度減半
            ValueAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall,
                    "height", newBall.getHeight()
                    , newBall.getHeight() - BALL_SIZE / 2);
            // 設定stretchAnim2動畫持續時間
            stretchAnim2.setDuration(duration / 4);
            // 設定squashAnim2動畫重複1次
            stretchAnim2.setRepeatCount(1);
            // 設定squashAnim2動畫的重複方式
            stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
            // 設定squashAnim2動畫的插值方式:減速插值
            stretchAnim2.setInterpolator(new DecelerateInterpolator());
            // 定義小球“彈起”的動畫
            ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(
                    newBall , "y", endY, startY);
            // 設定持續時間
            bounceBackAnim.setDuration(duration);
            // 設定動畫的插值方式:減速插值
            bounceBackAnim.setInterpolator(new DecelerateInterpolator());
            // 使用AnimatorSet按順序播放“掉落/壓扁&拉伸/彈起動畫
            AnimatorSet bouncer = new AnimatorSet();
            // 定義在squashAnim1動畫之前播放fallAnim下落動畫
            bouncer.play(fallAnim).before(squashAnim1);
            // 由於小球在“螢幕”下方彈起時,小球要被壓扁
            // 即:寬度加倍、x座標左移半個球,高度減半、y座標下移半個球
            // 因此此處指定播放squashAnim1的同時
            // 還播放squashAnim2、stretchAnim1、stretchAnim2
            bouncer.play(squashAnim1).with(squashAnim2);
            bouncer.play(squashAnim1).with(stretchAnim1);
            bouncer.play(squashAnim1).with(stretchAnim2);
            // 指定播放stretchAnim2動畫之後,播放bounceBackAnim彈起動畫
            bouncer.play(bounceBackAnim).after(stretchAnim2);
            // 定義對newBall物件的alpha屬性執行從1到0的動畫(即定義漸隱動畫)
            ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(newBall
                    , "alpha", 1f, 0f);
            // 設定動畫持續時間
            fadeAnim.setDuration(250);
            // 為fadeAnim動畫新增監聽器
            fadeAnim.addListener(new AnimatorListenerAdapter()
            {
                // 當動畫結束時
                @Override
                public void onAnimationEnd(Animator animation)
                {
                    // 動畫結束時將該動畫關聯的ShapeHolder刪除
                    balls.remove(((ObjectAnimator) animation).getTarget());
                }
            });
            // 再次定義一個AnimatorSet來組合動畫
            AnimatorSet animatorSet = new AnimatorSet();
            // 指定在播放fadeAnim之前,先播放bouncer動畫
            animatorSet.play(bouncer).before(fadeAnim);
            // 開發播放動畫
            animatorSet.start();
            return true;
        }
        private ShapeHolder addBall(float x, float y)
        {
            // 建立一個橢圓
            OvalShape circle = new OvalShape();
            // 設定該橢圓的寬、高
            circle.resize(BALL_SIZE, BALL_SIZE);
            // 將橢圓包裝成Drawable物件
            ShapeDrawable drawable = new ShapeDrawable(circle);
            // 建立一個ShapeHolder物件
            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            // 設定ShapeHolder的x、y座標
            shapeHolder.setX(x - BALL_SIZE / 2);
            shapeHolder.setY(y - BALL_SIZE / 2);
            int red = (int) (Math.random() * 255);
            int green = (int) (Math.random() * 255);
            int blue = (int) (Math.random() * 255);
            // 將red、green、blue三個隨機數組合成ARGB顏色
            int color = 0xff000000 + red << 16 | green << 8 | blue;
            // 獲取drawable上關聯的畫筆
            Paint paint = drawable.getPaint();
            // 將red、green、blue三個隨機數除以4得到商值組合成ARGB顏色
            int darkColor = 0xff000000 | red / 4 << 16
                | green / 4 << 8 | blue / 4;
            // 建立圓形漸變
            RadialGradient gradient = new RadialGradient(
                37.5f, 12.5f, BALL_SIZE, color, darkColor
                , Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            // 為shapeHolder設定paint畫筆
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);
            return shapeHolder;
        }
        @Override
        protected void onDraw(Canvas canvas)
        {
            // 遍歷balls集合中的每個ShapeHolder物件
            for (ShapeHolder shapeHolder : balls)
            {
                // 儲存canvas的當前座標系統
                canvas.save();
                // 座標變換:將畫布座標系統平移到shapeHolder的X、Y座標處
                canvas.translate(shapeHolder.getX()
                        , shapeHolder.getY());
                // 將shapeHolder持有的圓形繪製在Canvas上
                shapeHolder.getShape().draw(canvas);
                // 恢復Canvas座標系統
                canvas.restore();
            }
        }
    }
}

使用SurfaceView實現動畫

使用自定義View繪製圖片的 缺陷

  1. View缺乏雙緩衝機制
  2. 當程式需要更新View上的圖片時,程式必須重新繪製View上顯示的整張圖片
  3. 新執行緒無法直接更新View元件

    動畫三

    FishView程式碼:
public class FishView extends SurfaceView
        implements SurfaceHolder.Callback
{
    private SurfaceHolder holder;
    private UpdateViewThread updateThread;
    private boolean hasSurface;
    private Bitmap back;
    private Bitmap[] fishs;
    private int fishIndex = 0; // 定義變數記錄繪製第幾張魚的圖片
    // 下面定義2個變數,記錄魚的初始位置
    private float fishX = 778;
    private float fishY = 500;
    private float fishSpeed = 6; // 魚的遊動速度
    // 定義魚遊動的角度
    private int fishAngle = new Random().nextInt(60);
    Matrix matrix = new Matrix();
    public FishView(Context ctx, AttributeSet set)
    {
        super(ctx, set);
        // 獲取該SurfaceView對應的SurfaceHolder,並將該類的例項作為其Callback
        holder = getHolder();
        holder.addCallback(this);
        hasSurface = false;
        back = BitmapFactory.decodeResource(ctx.getResources()
                , R.drawable.fishbg);
        fishs = new Bitmap[10];
        // 初始化魚遊動動畫的10張圖片
        for(int i = 0 ; i < 10 ; i++)
        {
            try
            {
                int fishId = (Integer)R.drawable.class
                        .getField("fish" + i).get(null);
                fishs[i] = BitmapFactory.decodeResource(
                        ctx.getResources(), fishId);
            }
            catch(Exception e){
                e.printStackTrace();
            }
        }
    }

    public void resume()
    {
        // 建立和啟動影象更新執行緒
        if (updateThread == null)
        {
            updateThread = new UpdateViewThread();
            if (hasSurface == true)
                updateThread.start();
        }
    }

    public void pause()
    {
        // 停止影象更新執行緒
        if (updateThread != null)
        {
            updateThread.requestExitAndWait();
            updateThread = null;
        }
    }

    // 當SurfaceView被建立時回撥該方法
    @Override
    public void surfaceCreated(SurfaceHolder holder)
    {
        hasSurface = true;
        resume();
    }

    // 當SurfaceView將要被銷燬時回撥該方法
    public void surfaceDestroyed(SurfaceHolder holder)
    {
        hasSurface = false;
        pause();
    }

    // 當SurfaceView發生改變時回撥該方法
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h)
    {
        if (updateThread != null)
            updateThread.onWindowResize(w, h);
    }

    class UpdateViewThread extends Thread
    {
        // 定義一個記錄圖形是否更新完成的旗標
        private boolean done;
        UpdateViewThread()
        {
            super();
            done = false;
        }

        @Override
        public void run()
        {
            SurfaceHolder surfaceHolder = holder;
            // 重複繪圖迴圈,直到執行緒停止
            while (!done)
            {
                // 鎖定SurfaceView,並返回到要繪圖的Canvas
                Canvas canvas = surfaceHolder.lockCanvas();  // ①
                // 繪製背景圖片
                canvas.drawBitmap(back, 0, 0, null);
                // 如果魚“游出”螢幕之外,重新初始魚的位置
                if(fishX < 0)
                {
                    fishX = 778;
                    fishY = 500;
                    fishAngle = new Random().nextInt(60);
                }
                if(fishY < 0)
                {
                    fishX = 778;
                    fishY = 500;
                    fishAngle = new Random().nextInt(60);

                }
                // 使用Matrix來控制魚的旋轉角度和位置
                matrix.reset();
                matrix.setRotate(fishAngle);
                matrix.postTranslate(fishX -= fishSpeed * Math
                    .cos(Math.toRadians(fishAngle))
                    , fishY -= fishSpeed * Math.sin(Math.toRadians(fishAngle)));
                canvas.drawBitmap(fishs[fishIndex++ % fishs.length], matrix, null);
                // 解鎖Canvas,並渲染當前影象
                surfaceHolder.unlockCanvasAndPost(canvas);  // ②
                try
                {
                    Thread.sleep(60);
                }
                catch (InterruptedException e){}
            }
        }

        public void requestExitAndWait()
        {
            // 把這個執行緒標記為完成,併合併到主程式執行緒
            done = true;
            try
            {
                join();
            }
            catch (InterruptedException ex){}
        }

        public void onWindowResize(int w, int h){
            // 處理SurfaceView的大小改變事件
        }
    }
}

動畫四

MainActivity程式碼:
public class MainActivity extends Activity
{
    private SurfaceHolder holder;
    private Paint paint;
    final int HEIGHT = 320;
    final int WIDTH = 768;
    final int X_OFFSET = 5;
    private int cx = X_OFFSET;
    // 實際的Y軸的位置
    int centerY = HEIGHT / 2;
    Timer timer = new Timer();
    TimerTask task = null;
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final SurfaceView surface = (SurfaceView)
                findViewById(R.id.show);
        // 初始化SurfaceHolder物件
        holder = surface.getHolder();
        paint = new Paint();
        paint.setColor(Color.GREEN);
        paint.setStrokeWidth(3);
        Button sin = (Button)findViewById(R.id.sin);
        Button cos = (Button)findViewById(R.id.cos);
        OnClickListener listener = (new OnClickListener()
        {
            @Override
            public void onClick(final View source)
            {
                drawBack(holder);
                cx = X_OFFSET;
                if(task != null)
                {
                    task.cancel();
                }
                task = new TimerTask()
                {
                    public void run()
                    {
                        int cy = source.getId() == R.id.sin ? centerY
                                - (int)(100 * Math.sin((cx - 5) * 2
                                * Math.PI / 150))
                                : centerY - (int)(100 * Math.cos ((cx - 5)
                                * 2 * Math.PI / 150));
                        Canvas canvas = holder.lockCanvas(new Rect(cx ,
                                cy - 2  , cx + 2, cy + 2));
                        canvas.drawPoint(cx , cy , paint);
                        cx ++;
                        if (cx > WIDTH)
                        {
                            task.cancel();
                            task = null;
                        }
                        holder.unlockCanvasAndPost(canvas);
                    }
                };
                timer.schedule(task , 0 , 30);
            }
        });
        sin.setOnClickListener(listener);
        cos.setOnClickListener(listener);
        holder.addCallback(new Callback()
        {
            @Override
            public void surfaceChanged(SurfaceHolder holder, int format,