2012-09-14

Android 小程式 : 模擬物件的生命週期

這是格友 LAI 在 模擬拖曳小圖示的 Android 程式 這篇中所提出的一個問題 :
『想請問~ 如果我要在觸控一點座標時,顯示一張小圖 然後過幾秒消失
我該怎麼修改程式??』

剛好為我提供了一個練習的機會 ^_^
時間已晚,加上最近又很忙 ~ 我先把程式碼 po 上,有興趣者請自行 copy 程式碼。詳細的解說就待我有空時再來補上。

整個程式架構分為三個部份 :
MainActivity.java (主 Activity)
Draw_View.java (處理畫面顯示的 class)
Ob_thread.java (定義物件元素的 class)

程式功能 :
觸控螢幕任一點,於該座標產生一張小圖,可連續點擊產生更多的小圖,每張小圖的生命週期為 3 秒。按照點擊順序所產生的多張小圖,當每張小圖各自的生命週期結束時,亦會按順序而消失。如果您將物件元素的生命週期改成亂數產生,那麼每張小圖停留在螢幕的時間將會變得不一樣 !

MainActivity.java 的內容
package a.b.c;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new Draw_View(this));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}


Draw_View.java 的內容
package a.b.c;

import java.util.ArrayList;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;

public class Draw_View extends SurfaceView implements SurfaceHolder.Callback, Runnable {
   
   int touch_X, touch_Y;
    Bitmap bp = null;
    SurfaceHolder surfaceHolder;
    Canvas canvas;
    Paint paint;
    Thread DV_t;
    boolean flag;
    Ob_thread Ob = null;
    ArrayList<Ob_thread> ob_Array = new ArrayList<Ob_thread>();

    //建構子
    public Draw_View(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        InitialSetting();
    }

    protected void InitialSetting() {
        bp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
        paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setStrokeWidth(4);
        paint.setTextSize(25);
    }

    protected synchronized void DoDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        if (!ob_Array.isEmpty()) {
            //逐一將 Ob_thread 陣列清單中的元素繪出
            for(Ob_thread o:ob_Array) {
                if (o.ob_t.isAlive()) {
                    canvas.drawBitmap(bp, o.center_x, o.center_y, null);
                }
            }
            //移除 Ob_thread 陣列清單中生命期已結束的元素
            int i=0;
            while( i <= ob_Array.size()-1 ) {
                if (!ob_Array.get(i).ob_t.isAlive()) {
                    ob_Array.remove(i);
                    i--;
                }
                i++;
            } 
        }
        //顯示目前在畫面上的物件數量
        canvas.drawText("Elements = " + ob_Array.size(), 10, 30, paint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Ob = null;
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            touch_X = (int)event.getX();
            touch_Y = (int)event.getY();
    
            synchronized(surfaceHolder) {
                //新增元素到Ob_thread 陣列清單中
                Ob = new Ob_thread(touch_X - bp.getWidth()/2, touch_Y - bp.getHeight()/2);
                ob_Array.add(Ob);
            }
        }
        return true;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        // TODO Auto-generated method stub
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        DV_t = new Thread(this);
        flag = true;
        DV_t.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        flag = false;
        try {
            DV_t.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(flag) {
            try {
                canvas = surfaceHolder.lockCanvas();
                DoDraw(canvas);
                //每 0.2 秒更新一次螢幕顯示
                Thread.sleep(200);
                surfaceHolder.unlockCanvasAndPost(canvas);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}


Ob_thread.java 的內容
package a.b.c;

public class Ob_thread implements Runnable {

    Thread ob_t;
    int center_x, center_y;

    //建構子
    public Ob_thread(int x, int y) {
        this.center_x = x;
        this.center_y = y;
        ob_t = new Thread(this); 
        ob_t.start();
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            //生命週期 3 秒 (單位 : 千分之一秒) 
            //改變此數值可決定物件元素停留在畫面的時間
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}


執行結果必須以動態方式來觀察才能體會,勉強附上兩張執行過程的靜態圖 ... 乍看之下很像一堆蟑螂吧 ? XD
這是刻意把生命週期改成 10 秒才能在一定的時間內點出這麼多隻的啊 !
生命週期只設 3 秒的話 ... 點個幾隻再切換到 DDMS 中拍照肯定來不及,會跑(死)光光 ~ XD

33 則留言:

  1. 太神奇了~~
    謝謝您的 分享拉^^

    (不過真的很像蟑螂XDDDD

    回覆刪除
    回覆
    1. 我發現剛剛的回覆離題的太嚴重了 (挖洞躲起來 XD)
      忽然發現 ... 你應該不是用 Widget 來 layout 的,對嗎 ? 我有說錯的話請更正喔 !

      你是用 xml 的方式 layout 畫面的還是用程式碼來 layout 畫面的呢 ? 如果不想背景圖被前方的元件影響 (不論新增或移除前方的元件) 解決原理 : 當前方元件的位置被更動時,必須先重繪(重貼)背景圖,接著再由下往上(互相遮掩的順序)重繪出各元件,就 OK 了 !

      練習題二 ... 預備開始 ! Go ~~~ XD (跌倒)

      刪除
  2. 達仔~又來請教一些基礎問題@@
    關於android imageview
    我想要放一張圖片當作背景圖 不管新增什麼按鈕~TextView
    都不會影響背景圖 我該怎麼做??
    我現階段做的 變成是 TextView一個區塊 圖一個區塊

    不能融合阿><

    (達仔有G+嗎??

    回覆刪除
    回覆
    1. 我有 G+ ~ 但我都放著沒用 XD
      你會這樣問 ... 是願意加我嗎? 真是太感謝了 T皿T
      請讓我先弄懂 G+ 後再+你吧 ^_^ (因為我根本不懂怎麼使用 G+ 的功能 哈哈哈)

      使用現成的 Widget 來開發 App 是很方便,但它畢竟還是有些限制存在,無法隨心所欲 ~ 如果你無法克服 Widget 的限制,不妨退而求其次改變設計方向,或是擺脫 Widget 的糾纏,自行以 View 或 SurfaceView 之類的類別來規劃你的 App 畫面,運行速度也比 Widget 快很多。

      上述所言好像都沒有切中你的問題重點 XD 因為我之前用 Widget 設計時用到快殺人 XDDD 一整個抓狂 ~ 雖然目前新版的 Widget 已做了很大的改善,但 ... 我就是回不去了 T皿T

      刪除
  3. 我要問的問題
    我找到解答了
    哈哈


    我還是個初學者 我都只能靠著買書
    看書上範例來練習了
    對於那些屬性 其實我真的都不了解
    只能一直問人 尋求解答
    但為什麼這樣做 卻又一知半解的
    我該怎麼學習><

    回覆刪除
    回覆
    1. 剛開始 ~ 一堆屬性或語法看不太懂這很正常 ^_^
      不過,一本好書的引導的確很重要!
      國內大部分寫 Android 方面的書 ...嗯 ~ No Comment !

      我推薦這本 : (不必去找 1 ~ 3 版的,內容幾乎都一樣)
      Google!Android 3手機應用程式設計入門(第四版)
      作者 : 蓋索林

      如果你 Java 的底子還不足的話,建議你花點時間打好基礎 (尤其是執行緒的觀念與應用),雖然有些人(作者)會很樂觀地跟你(讀者)說 : 一邊學 Android 一邊學 Java 並不衝突,但是說這番話的人都是本身有程式設計(Java 或 C/C++)底子的人。我說的也不一定對啦! 請自行斟酌 ^^

      另外; Android Developer 官網的 Android Training 請務必要仔細看看,網址如下:
      http://developer.android.com/training/index.html
      雖然都是英文,就算要查字典也要想辦法盡量搞懂(最好不要用翻譯網頁功能的軟體來讀,有些段落會被翻譯到嚴重偏離原意),裡面的官方文件畢竟是最新的,坊間的書籍再新也比不上 Android 官網更新的速度。

      國內不曉得有沒有相關的論壇或網站,我一直沒去尋找,我都偏向『閉門造車』的學習方向,這點實在不值得學習 XD

      隨時歡迎你一起來討論 ^_^ 加油

      刪除
    2. 哈 看得出來
      您的JAVA底子很強
      像這題程式 您的解答 我就一知半解了@@
      我的JAVA 如果不看書或網路
      大概就死光光了QQ

      刪除
    3. Java 我並沒有學得很深入,才剛學會走幾步路而已我就想飛看看了 XD

      希望不會摔死啊 ~~~ XDDD

      刪除
  4. 達仔 我來懺悔
    其實 我說錯讓您誤會了
    我想表達的是~讓一張圖當我的程式底圖
    其實 我本來也是想錯
    那個融合的東西 我想法根本是錯
    讓你誤會了><

    回覆刪除
    回覆
    1. 沒關係啦! 你不需要介意 ^^
      還好你有找到你要的答案,我才能逃過一劫 XD

      雖然 Apps 種類不同,但是每個 App 專案的架構差異並不大,最大的差別就在於你會使用哪種方式來佈局你的畫面了。

      由於我是朝向以遊戲為開發重點,所以畫面佈局都只著重在程式碼(類別)而非 xml 檔格式。因此,Android 開發工具所提供的 Widget (如 Button、TextView ...) 我幾乎不太用的到。

      刪除
    2. 其實 我想問您
      一開始您在接觸時
      都是如何去學習Android的
      像我現在都是看書練習
      可是有人跟我說
      我的基礎觀念都沒有
      範例練習再多次都一樣的
      觀念沒有還是沒有~QQ

      刪除
    3. 我接觸 Android 之前已經有 C/C++ 程式的基礎了(Java 與 C 語言很類似,所以 Java 學起來很快)

      我學習 Android 的步驟大略如下 :
      1. 看 Android 方面的書 (似懂非懂)
      2. 重新學 Java (自學二 ~ 三個月,搞懂執行緒)
      3. 再回頭看 Android (先搞懂 xml 檔是啥東東)
      4. 再加強『物件導向』設計的觀念 (看書)

      如果你沒有程式設計的基礎,建議你先去弄懂了再回頭來學 Android 還不遲 ! 假設你硬著頭皮在 Android 中打滾,或許也可以搞出一些名堂也說不定 ! 但是 ~ 基礎與觀念仍是薄弱,這樣你能涉入的範圍畢竟有限。

      提醒你 ~ 學會 Java 基礎之後其實就可以應用在 Android 上了,不需要涉入 Java 太深 ! 但是 Android 有它自己的一套架構與 Java 又不太一樣,但至少你對程式架構的觀念清楚後,阻礙自然就小囉 !

      若你對程式設計完全不懂,那麼這樣的方向你可以參考看看 :
      1. Java 入門 (『物件導向』與『執行緒』的觀念一定要學起來)
      2. 何謂 xml 檔、何謂資源檔 (文字、圖片、聲音、layout 檔 ...)
      3. Android 的架構

      至於 Android 書上的練習題,等你清楚了這三項再回頭來看,應該會豁然開朗 ^^

      學習沒有速成,有心一定都能克服 ! 共勉之 ~
      我也還在努力中 ...

      刪除
    4. 達仔 想請教您 關於觸控 您了解嗎??
      我現在寫好一個簡單的 觸碰螢幕的一點 就會跳到另外一個視窗(或是網頁) 但是 我只觸碰螢幕一次 程式卻會觸發好幾次 怎麼會這樣???

      刪除
    5. 達仔 最近有空幫我看一下程式碼嗎??
      想不通我哪邊錯了~

      刪除
    6. 觸控我都習慣用 onTouchEvent() 方法來處理,另外我記得沒錯的話好像還有 onKeyDown ... 之類的(記不太起來),把完整程式碼記過來吧! 我幫你看看 ~~~
      hst35988712@gmail.com (希望沒記錯 mail XD)
      還有 ... 不要再用敬語 --"您"-- 了啦! 這樣好有距離感啊 ~~~ >"<

      刪除
    7. 對了 ~ 萬一你的程式碼我有看沒有懂的話 ... (逃) XD
      要換你指點我了喔 ^_^

      刪除
    8. 收到了 ~
      大致看了一下 ...
      針對你說觸控後會造成觸發好幾次的問題,你先試著把 switch(event.getAction())那一段的 ACTION_MOVE 拿掉,再試看看! 如果你要明確地判斷 "手按下" 或 "手離開" 的動作最好不要把 ACTION_MOVE 扯進來!

      其他的部分大都是判斷座標位置的部份 @@,你特別用分隔線隔成四個部份的地方 ... 其中第二段卻使用gTouchX 作為判斷依據,與另三個部分用 HoldX(Y)不同 ... 就經驗上來說有點詭異 XD 不過或許有你特別的用意吧!

      我再繼續仔細檢查看看...

      刪除
    9. 很慚愧的跟您說
      我.......用複製貼上的 所以 有一些地方沒改到

      刪除
    10. 我對那四個變數 poor(1,2,3,4) 實在很好奇,它們的功能是 ??? 有遺漏的話不妨重寄一次吧 !

      題外話 : (好消息)
      今天我收到 Google Checkout 的來信通知,往後在 Google Play 販售的 App 收益不必再透過 AdSense 來收取款項囉! 所以原本在台灣的 Android Apps 開發者不必為了取得 AdSense 的資格而傷腦筋了! 放煙火吧 ~~~~ 咻 ~~~ PON ~~~ 咻 ~~~ PINPON ~ ^口^

      刪除
  5. poor只用到兩個 後面是多的XDD
    poor是用來算我第一隻手指頭跟第一個指定座標的距離差

    AdSense 資格很難取得?

    回覆刪除
    回覆
    1. 你沒有申請過AdSense嗎? (真不公平 >"<)
      AdSense出了名的難申請,陣亡的一堆 x_x
      你如果想挑戰申請AdSense的話可以玩一下 XD
      https://www.google.com/adsense/v3/app?hl=zh_TW

      你可以使用 Rect 類別(並搭配 contains() 方法)來做觸控範圍的檢查,這樣可以縮短你程式碼的長度,並提高可讀性。
      http://developer.android.com/reference/android/graphics/Rect.html

      刪除
    2. ㄆ 我還是不要當勇者!!!!

      Rect這有什麼作用??
      這個BUG 真的很煩
      我只是觸碰一次
      卻跑出快7-8個畫面出來QQ

      刪除
    3. 我剛剛寄了目前我完成的部份了
      ^^

      刪除
  6. Rect 是一種"矩形"類別... 如果沒完成Debug跟你說什麼你也一定聽不下去,有空你再去研究 Rect 好了 ~

    可否請你把 main.xml 檔的內容寄過來,我懶得用圖形介面去拉那些元件了 ~ 多一個人做實機測試 Debug 比較快!

    回覆刪除
    回覆
    1. 寄出了~~阿你阿豆
      我的程式碼 我都用平板(ASUS變型金剛II)做測試所以 座標可能有誤差

      刪除
  7. int [][] a ={{8,100},{2,50},{3,900},{9,120}};
    想請問如何讓他變成{2,50} {3,900} {8,100} {9,120}
    這樣的陣列~~
    程式碼該怎麼寫
    問題多了一點><

    回覆刪除
    回覆
    1. 你是說在程式的執行階段去更改陣列的內容嗎?
      你必須指定第一維和第二維陣列的 Index 之後再將值存入。
      譬如 :
      a[0][0] = 2;
      a[0][1] = 50;
      .....

      如果要在二維陣列中做資料交換... 很不好處理,你只能先設個暫存變數,然後再一一做交換。

      直接用二維陣列去做資料交換較麻煩,所以我才會建議你用 Rect 類別來處理具有X、Y座標的數據。

      你傳過來的apk檔我無法直接套用到Eclipse上,我只好重構整個程式碼,還在努力中 ~

      刪除
    2. 二維陣列去做資料交換 我目前寫完了

      刪除
    3. 我再不出現的話就要變成程式逃兵了 XD
      恭喜你 ^_^ 完成二維資料交換
      不知你的程式是否 Debug 完成 ?

      我重新撰寫了程式,功能可能與你原先的不同。
      本來昨天就寫好了,卻在不斷修飾的情況下搞到欲罷不能、自找麻煩 XD 這支程式可以支援各種不同尺寸的螢幕。

      總之 ~ 先寄給你過目一下! 可以的話 ~ 就讓我將它 PO 到網誌中吧!

      刪除
    4. MotionEvent.setAction(ACTION_CANCEL);有錯誤
      我的程式碼依舊是觸發好多次= =
      快哭了

      刪除
  8. 達仔說的 是 event.setAction(MotionEvent.ACTION_CANCEL);
    這樣嗎??

    回覆刪除
    回覆
    1. 這個用法是我當時搞錯的 ~ 造成誤導 XD
      你會在 Gmail 中收到這則回覆是因為我剛剛在網誌這邊發現的,我習慣會回覆網誌上的訪客留言 ! 即使賞味期限已經過期 XDD

      刪除

搜尋此網誌