1. 程式人生 > >Android-多點觸控的實現示例詳解

Android-多點觸控的實現示例詳解

最近在做一個多點觸控的專案,因為之前沒有做過相同的,就先寫了一個示例程式碼。其中涉及了一些常用的實現方法,在此分析一下,和大家分享。
這個示例涉及到的內容:

1.靜態設定橫屏的方法;
2.設定全屏的方法;
3.在onCreate()方法中獲取圖片高度和寬度的方法;
4.多點觸控。

示例比較簡單,先把原始碼放上。

import android.annotation.SuppressLint;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import
android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.RelativeLayout; public class MainMultiTouch extends
Activity {
private ImageView img1; private ImageView img2; private RelativeLayout rela; private int widthImg1; private int heightImg1; private int widthImg2; private int heightImg2; private int locImg1X = 0; private int locImg1Y = 0; private int locImg2X = 0; private
int locImg2Y = 0; private boolean hasMeasured = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags( WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main_multi_touch); initView(); // getImagePixHandler.sendEmptyMessageDelayed(0, 200); } /** * initialize the views */ private void initView() { img1 = (ImageView) findViewById(R.id.imageView1); img2 = (ImageView) findViewById(R.id.imageView2); rela = (RelativeLayout) findViewById(R.id.mainlayout); rela.setOnTouchListener(new OnLayoutTouchListener()); //監聽繪畫完成後測量圖片的長寬 ViewTreeObserver vto = rela.getViewTreeObserver(); vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { public boolean onPreDraw() { if (hasMeasured == false) { widthImg1 = img1.getWidth(); heightImg1 = img1.getHeight(); widthImg2 = img2.getWidth(); heightImg2 = img2.getHeight(); locImg1X = (int) img1.getX(); locImg2X = (int) img2.getX(); locImg1Y = (int) img1.getY(); locImg2Y = (int) img2.getY(); hasMeasured = true; } return true; } }); } /** * get the width&height and visual position of imageviews,in pixels */ @SuppressLint("HandlerLeak") private Handler getImagePixHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0) { // The width of your view, in pixels widthImg1 = img1.getWidth(); widthImg2 = img2.getWidth(); heightImg1 = img1.getHeight(); heightImg2 = img2.getHeight(); // The visual x&y position of imageviews, in pixels. locImg1X = (int) img1.getX(); locImg2X = (int) img2.getX(); locImg1Y = (int) img1.getY(); locImg2Y = (int) img2.getY(); rela.setOnTouchListener(new OnLayoutTouchListener()); } } }; /** * * @author dell * */ class OnLayoutTouchListener implements OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { int x; int y; int idPoint = event.getActionIndex(); x = (int) event.getX(idPoint); y = (int) event.getY(idPoint); Boolean flag1 = x > locImg1X && x < (locImg1X + widthImg1) && y > locImg1Y && y < (locImg1Y + heightImg1); Boolean flag2 = x > locImg2X && x < (locImg2X + widthImg2) && y > locImg2Y && y < (locImg2Y + heightImg2); // switch(event.getAction() & MotionEvent.ACTION_MASK){ switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // 第一個手指按下事件 if (flag1) { // System.out.println("第一個手指的按下1"); System.out.println("按下1"); } if (flag2) { // System.out.println("第一個手指的按下2"); System.out.println("按下2"); } break; case MotionEvent.ACTION_POINTER_DOWN: // 第二個手指按下事件 if (flag1) { // System.out.println("第二個手指的按下1"); System.out.println("按下1"); } if (flag2) { // System.out.println("第二個手指的按下2"); System.out.println("按下2"); } break; case MotionEvent.ACTION_UP: // 第一個手指擡起事件 if (flag1) { // System.out.println("第一個手指的擡起1"); System.out.println("擡起1"); } if (flag2) { // System.out.println("第一個手指的擡起2"); System.out.println("擡起2"); } break; case MotionEvent.ACTION_POINTER_UP: // 第二個手指擡起事件 if (flag1) { // System.out.println("第二個手指的擡起1"); System.out.println("擡起1"); } if (flag2) { // System.out.println("第二個手指的擡起2"); System.out.println("擡起2"); } break; case MotionEvent.ACTION_MOVE: // 手指滑動事件 break; } return true; } } }

然後根據上面的4點一次說明一下。
1.設定橫屏的方法
靜態設定可以通過在Manifest檔案中的對應的Activity的屬性中新增如下程式碼:

android:launchMode="singleTask"
android:screenOrientation="landscape"

OK!
順便提一下,在電腦編輯layout檔案的時候,可以按照下圖的點選按鈕,即可將佈局檔案更改為橫屏模式。
這裡寫圖片描述

2.設定全屏的方法
設定全屏只需要在onCreate()方法中新增如下程式碼

requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(
        WindowManager.LayoutParams.FLAG_FULLSCREEN,
        WindowManager.LayoutParams.FLAG_FULLSCREEN);

需要注意的是這段程式碼和setContentView()方法的相互位置。如果放在setContentView()的後面就會報錯!

3.在onCreate()方法中獲取圖片高度和寬度的方法。
可以自己寫程式碼嘗試一下:

public class DrawTest extends Activity {

    private ImageView img1;
    private ImageView img2;

    private int widthImg1;
    private int heightImg1;
    private int widthImg2;
    private int heightImg2;
    private int locImg1X = 0;
    private int locImg1Y = 0;
    private int locImg2X = 0;
    private int locImg2Y = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_multi_touch);
        img1 = (ImageView) findViewById(R.id.imageView1);
        img2 = (ImageView) findViewById(R.id.imageView2);
        img1.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(DrawTest.this,MainMultiTouch.class);
                startActivity(intent);
            }
        });
        printInfo(0);

    }

    @Override
    protected void onStart() {
        super.onStart();
        printInfo(1);
    }

    @Override
    protected void onRestart() {
        super.onRestart();
    }
    @Override
    protected void onResume() {

        super.onResume();
        printInfo(2);
    }
    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        printInfo(3);
    }
    @Override
    protected void onStop() {
        super.onStop();
        printInfo(4);
    }
    @Overridz
    protected void onDestroy() {
        super.onDestroy();
    }
    private void printInfo(int i){

        switch(i){
        case 0:
            //onCreate
            System.out.print("onCreate->");
            break;
        case 1:
            System.out.print("onStart->");
            break;
        case 2:
            System.out.print("onResume->");
            break;
        case 3:
            System.out.print("onPause->");
            break;
        case 4:
            System.out.print("onStop->");
            break;
        }
        widthImg1 = img1.getWidth();
        heightImg1 = img1.getHeight();
        widthImg2 = img2.getWidth();
        heightImg2 = img2.getHeight();

        locImg1X = (int) img1.getX();
        locImg2X = (int) img2.getX();
        locImg1Y = (int) img1.getY();
        locImg2Y = (int) img2.getY();
        System.out.println("widthImg1 = " + widthImg1);
        System.out.println("heightImg1 = " + heightImg1);
        System.out.println("widthImg2 = " + widthImg2);
        System.out.println("heightImg2 = " + heightImg2);
        System.out.println("locImg1X = " + locImg1X);
        System.out.println("locImg2X = " + locImg2X);
        System.out.println("locImg1Y = " + locImg1Y);
        System.out.println("locImg2Y = " + locImg2Y);
    }
}

執行結果如下圖:
獲取圖片高度實驗結果
這裡寫圖片描述

由結果可以看出,只有在onResume()方法後才能獲取圖片的高度,否則為0。是因為在onResume()方法中會呼叫onDraw()方法。
有兩個方法可以獲取圖片的高度和寬度:、
(1)使用執行緒延時:即將獲取圖片高度的方法放到另外一個執行緒,等主執行緒執行一段足夠長的時間以確保其能執行onDraw()方法。
具體實現方法如上面方法中的handler。這種實現方法的最大問題是,多長的時間算是足夠長的時間?因為不同的編譯器執行速度不同,為了確保程式在各個裝置都能正常執行,這個時間一般取得比較大,例如我取值為200(ms).
(2)監聽onDraw()方法,
參考下面網站:
http://www.cnblogs.com/wt616/archive/2012/05/11/2496180.html
如上面程式中的,通過設定一個監聽器,可以實現。

4.多點觸控
對於這個實現方法,主要集中下面這幾對方法或常數的意義:
(1)getAciton() & getActionMasked()
(2)getX & getX(index)
(3)ACTION_DOWN & ACTION_POINTER_DOWN
首先我們先來看看其官方文件的解釋:

Some devices can report multiple movement traces at the same time. Multi-touch screens emit one movement trace for each finger. The individual fingers or other objects that generate movement traces are referred to as pointers. Motion events contain information about all of the pointers that are currently active even if some of them have not moved since the last event was delivered.

The number of pointers only ever changes by one as individual pointers go up and down, except when the gesture is canceled.
Each pointer has a unique id that is assigned when it first goes down(indicated by ACTION_DOWN or ACTION_POINTER_DOWN). A pointer id remains valid until the pointer eventually goes up(indicated by ACTION_UP or ACTION_POINTER_UP) or when the gesture is canceled (indicated by ACTION_CANCEL).

The order in which individual pointers appear within a motion event is undefined. Thus the pointer index of a pointer can change from one event to the next but the pointer id of a pointer is guaranteed to remain constant as long as the pointer remains active. Use the getPointerId(int) method to obtain the pointer id of a pointerto track it acro ss all subsequent motion events in a gesture. Then for successive motion events, use the findPointerIndex(int) method to obtain the pointer index for a given pointer id in that motion event.>

這段英文是官方API 對於MotionEvent的概述內容。其中涉及到兩個重要的概念,就是“Motion Event”和 “Pointer”。
(1)Pointers:系統對每個引起引動追蹤的手指或者其它物品稱作“觸點”。
而這個點的位置就是接觸面積的中心。而後面的討論都是建立在“觸點”的概念基礎上的。
(2)Motion events:包含目前正處於活動狀態的各個點的資訊。

這些資訊具體指什麼呢?

(1)pointer index
(2)pointer id
(3)pointer position
對於這三個屬性,pointer position描述的是觸點在整個螢幕的絕對座標位置,座標系的原點是螢幕的左上角,單位是pixel即畫素點。
而index和id就比較容易混淆。
index在每個動作都有可能發生變化。比方說,你手指按下後擡起,按下時和擡起時的index就可能不一樣。
id則是一旦這個點生成就一直保持不變,直到手指擡起之後才會失效。也就是說,你的手指一旦落下,然後可以移動,最後擡起,這一系列過程中,這個隨著你手指的點的id是保持不變的。但是相反的,每次event的index就可能不相同。
那麼index是如何編號的呢?系統會在每次動作發生時從0開始的空閒的整數中的最小的數賦值給index。

如何獲得這些資訊呢?

pointer index:

int getActionIndex ();
int findPointerIndex(int pointerId);

pointer id:

int getPointerId(int pointerIndex);

pointer position

getX(); getY();
getX(int index); getY(int index);

getX() 與getX(int)有什麼區別呢?
看一下API就瞭然了!

getX(int):
Returns the X coordinate of this event for the given pointer index;
getX(i):
getX(int) for the first pointer index

最後就是getAction() 與getActionMasked()的區別了。這個可以參考我上篇轉載的文章,那篇作者講解的很詳細了。