2012-05-09

模擬拖曳小圖示的 Android 程式

這是我自己出給自己做的練習題之一 ! 此程式只是一個小功能的試作, 並不是一個完整的應用程式, 會把程式碼貼出來僅是提供給有興趣者做參考而已.

主要功能 :
可以用滑鼠隨意拖曳螢幕上的小圖示; 若是在手機上, 則可以用手指拖曳.

操作方式 : (以模擬器為例)
以滑鼠左鍵按住小圖示不放, 再移動滑鼠軌跡拖曳小圖示, 放開滑鼠左鍵後, 小圖示則會停留在新的位置上.

程式技巧重點 :
當按住小圖示的任何一個座標位置時, 程式會即時記下被按住的座標點位置, 即使在拖曳的過程中, 圖示本身不斷變動的座標仍會與被按住的座標點保持相對的距離, 讓整個拖曳過程極為流暢自然 ! 我刻意在螢幕上標出各變數的值, 方便在拖曳時觀察各變數的數值變化.

螢幕上的各變數值解說 :
Tx 與 Ty : 當你按下小圖示時, 對應於整個螢幕的實際座標.
takerect(Left,Top) : 小圖示的左上角對應於整個螢幕的實際座標.
chk : 小圖示被按住的時候為 true, 否則為 false.
tempX,Y :  小圖示左上角座標值 與 被按住的座標點 兩者間的差距數值; 其實就是 tempX = Tx - takerect(Left),   tempY = Ty - takerect(Top)

[ 拖曳功能 ] 常被普遍運用在視窗作業環境中

程式碼如下 :

// 主程式
public class practise1 extends Activity {
    /** Called when the activity is first created. */
   
    TakeView takeView;
   
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
       
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        takeView = new TakeView(this);
        setContentView(takeView);
    }
}

===========================================

// 另一個類別
public class TakeView extends SurfaceView implements SurfaceHolder.Callback {

    SurfaceHolder holder;
    Bitmap takebp;
    Paint paint;
    Rect takerect;  //矩形容器
    int x,y;
    ShowThread st;
    int touchX=0,touchY=0;
    boolean chk = false;
    int tempwidth=0;
    int tempheight=0;
   
    //constructor
    public TakeView(practise1 context) {
        super(context);
        // TODO Auto-generated constructor stub
        getHolder().addCallback(this);
        takebp = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
        //指定矩形容器的大小等於圖片takebp的大小
        takerect = new Rect(x,y,takebp.getWidth(),takebp.getHeight()); 
        holder = getHolder();
        st = new ShowThread();
        paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setTextSize(20);
    }
   
    public void DoDraw(Canvas canvas) {
        canvas.drawColor(Color.BLACK);
        canvas.drawBitmap(takebp, x, y, null);
        canvas.drawText("Tx = " + touchX + " Ty = " + touchY ,20, 100, paint);
        canvas.drawText("takerect(Left,Top):" + takerect.left + "," + takerect.top , 20, 150, paint);
        canvas.drawText("chk : " + chk, 20, 200, paint);
        canvas.drawText("tempX,Y : " + tempwidth + "," + tempheight, 10, 250, paint);
    }
   
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN &&
            takerect.contains((int)event.getX(),(int)event.getY())) {
            touchX = (int)event.getX();
            touchY = (int)event.getY();
            tempwidth = touchX - x;
            tempheight = touchY -y;
            chk = true;
        }
        else if ( (event.getAction() == MotionEvent.ACTION_MOVE) && chk) {
            touchX = (int)event.getX();
            touchY = (int)event.getY();
            //讓點擊的座標成為圖片的中心點
            //takebp.getWidth()/2;
            //takebp.getHeight()/2;
            x = touchX - tempwidth;
            y = touchY - tempheight;
            takerect.offsetTo(x, y);
        }
        else if (event.getAction() == MotionEvent.ACTION_UP) {
            chk = false;
        }
        return true;
    }
   
    public class ShowThread extends Thread {
      
        Canvas canvas;
        boolean flag = false;
        int span = 20;
       
        //constructor
        public ShowThread() {
            flag = true;
        }
       
        public void run() {
            while(flag) {
                try {
                    synchronized(holder) {
                        canvas = holder.lockCanvas();
                        DoDraw(canvas);
                        holder.unlockCanvasAndPost(canvas);
                        Thread.sleep(span);
                    }
                } catch (Exception e) { e.printStackTrace(); }
            }
        }
    }

    @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
        st.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // TODO Auto-generated method stub
        st.interrupt();
    }
}

18 則留言:

  1. 奇怪ㄋㄟ
    是小陳的電腦壞掉了嗎?
    為什麼拖不動@@

    回覆刪除
    回覆
    1. = =|||
      我想你是故意搞笑的吧? 文章內的那張圖是在執行的時候由模擬器的畫面中擷取下來的,它只是一張照片,如果要在文章內放上可以與讀者互動的視窗,必須撰寫 JavaScript 程式並以內嵌的方式掛到文章中。
      你真的誤會了 ~ QQ

      刪除
  2. 想請問~ 如果我要在觸控一點座標時,顯示一張小圖 然後過幾秒消失
    我該怎麼修改程式??

    回覆刪除
    回覆
    1. 這條程式並不適合改成你題意中的要求 ^^
      只要善用執行緒(Thread),很容易就可以完成。

      不容易一時說清楚 ~ 不妨就讓我當作練習題吧!寫好後,我再PO上來給大家參考一下 ^_^

      不然 ~ 來比賽 ... 看誰先寫好 XD

      刪除
    2. 哈 我只是個初學者@@
      怎麼敢跟您比賽~

      刪除
    3. 沒有啦 ~ 只是開個小玩笑而已,希望你不要介意喔 ! :p
      學習程式並沒有先來後到或是資歷深淺的問題,只要肯用心,願意花時間鑽研,功力一定會進步 !
      其實,Android Apps 開發,我仍在摸索中 ~ 希望能透過這個網誌與同好們一起教學相長 ^_^
      這幾天較忙 ~ 今日抽空將這題寫好了 ! 現在就將它 PO 出來囉 ! 請參考看看吧 ^_^

      http://dazi2012.blogspot.tw/2012/09/android-app.html

      刪除
  3. 請問如果我要在client端抓取手指划動的路徑or坐標位移 然後傳到另一台android server端的設備讓他接收 然後去移動滑鼠 就是遠端滑鼠的感覺 只不過是用android控制android 這樣我在client端要抓取什麼參數阿 我可以用motionEvent抓取x跟y 然後一直傳到server 可是我不太曉得server的滑鼠要如何動作,以及server如何生出滑鼠的圖示

    回覆刪除
    回覆
    1. 嗯 ~ 我懂你的意思
      這與 Windows 的遠端桌面遙控是同樣的原理。
      你必須同時在 client 端與 server 端安裝相對應的程式來回應兩者間的訊號互傳,理論上是可行的。但這已超出我目前所學的範圍,很抱歉 ! 沒能幫到你的忙 ~ 尚請見諒

      刪除
  4. 請問一下,如果想要在Android上放置一小圖片,但是位置座標希望能使用程式改變圖片位置,請問存放圖片吃屎座標的資料,是在哪一個文件裡頭?
    (背景是一張平面圖)

    回覆刪除
    回覆
    1. 以下假設你用的是 eclipse 編輯器 :
      在左側的專案瀏覽視窗中依序找到如下的資料夾名稱與路徑 : 例 ~
      res --> drawable-mdpi --> ic_launcher.png

      第二個資料夾請任選一個 (drawable-hdpi 或 drawable-ldpi 或 drawable-mdpi 或 drawable-xhdpi),系統會依照你設定的螢幕解析度自行選用適合的圖檔。

      ic_launcher.png 這是系統預設的圖檔,你可以將自己的圖檔放到這裡面來 (可用拖曳圖檔的方式加入),請注意一下圖檔支援的格式 (jpg、png、bmp ... 忘了 XD)

      刪除
  5. 打錯,是初始座標 手誤阿!!!!!!!!!!!!!!!!!
    (一張平面地圖)

    回覆刪除
    回覆
    1. 『吃屎』座標 ~~ 哈哈哈哈 ~
      謝謝你這暴笑的筆誤,讓我心情大好 ^_^

      刪除
  6. 在請問一下,假設我要製作一個圖片會隨者信號移動
    那我該如何在圖片上設定座標?

    就是底圖是一張手繪地圖,然後用另一小張圖當移動物
    請問是底圖設定座標嗎?
    要如何設定座標?

    回覆刪除
    回覆
    1. 抱歉啊 ~ 先前誤解了你的意思,答非所問了 :P
      (以前我用 C 語言寫過類似的東西 -- 仿遊戲 RPG 角色在地圖上移動)
      以下假設手繪地圖有超過螢幕範圍 ~

      我會這樣做 : (我只能大略說明原理)
      原理是: 移動時,實際上只是不斷的改變大圖顯示的座標而已,小圖則固定在螢幕中央,設定座標的方式就是將這張手繪地圖的 長、寬 先做等比的數值量化 ... 換句話說就是以小圖的所在位置做為中心點,然後將可見的矩形範圍(螢幕)顯示出來,Android 在設定或處理超出螢幕範圍的圖形時並不會發生錯誤 (Exception) 這點相當貼心。

      另一個更簡單的方式 :
      移動時,直接變動手繪地圖的左上角座標並顯示出來 (Android 座標給負值不會發生錯誤),一樣可以達到目的,這招最簡單!

      刪除
    2. 那圖片有辦法隨著信號而移動嗎?

      例如說 我的小圖現在在 171.321 這裡
      之後信號發送 171.425的位置
      圖片有辦法自動移動到該位置嗎?

      刪除
    3. 只要是涉及運算的部份,程式設計者必須在程式中自行處理座標計算與 PO 圖的位置指定。
      垂直: 請更動 Y 軸座標
      橫向: 請更動 X 軸座標

      再度提醒 ~ 如果沒有對地圖做『等比的數值量化』(類似地圖的比例尺換算)就算你的小圖示移動了,也可能會標示在不正確的位置上。

      我僅能提供概念想法予你參考,詳細的程式碼請加油 !
      ^_^

      刪除
  7. 有像 http://dazi2012.blogspot.tw/2012/09/android-activity.html#more
    的完整程式碼嗎

    回覆刪除
    回覆
    1. 文章所列出的就是完整的程式碼囉 !
      此篇是較早之前所 PO 的文,程式碼沒有用表格框起來而已

      刪除

搜尋此網誌