Android之Gestures(手勢)

本節引言:

在應用中通過手勢來操作會大大提升用戶體驗, 比如Scroll手勢在瀏覽器中個滾屏,Fling在瀏覽器中的換頁等!

當然,有利也有弊,比如不當的手勢操作引起APP Carsh,經常這樣可是會引起用戶不滿的! 所以是否要為你的應用增加手勢,可要考慮清楚哦!另外手勢要和前面學的單指/多指觸碰相區分哦!

手勢是:連續觸碰的行為,比如左右上下滑動屏幕,又或者畫一些不規則的幾何圖形! Android對上述兩種手勢行為都提供了支持:

  • Android提供手勢檢測,并為手勢識別提供了相應的監聽器!

  • Android運行開發者自行添加手勢,并且提供了相應的API識別用戶手勢!

1.Android中手勢交互的執行順序

1.手指觸碰屏幕時,觸發MotionEvent事件!

2.該事件被OnTouchListener監聽,可在它的onTouch()方法中獲得該MotionEvent對象!

3.通過GestureDetector轉發MotionEvent對象給OnGestureListener

4.我們可以通過OnGestureListener獲得該對象,然后獲取相關信息,以及做相關處理!

我們來看下上述的三個類都是干嘛的:MotionEvent: 這個類用于封裝手勢、觸摸筆、軌跡球等等的動作事件。 其內部封裝了兩個重要的屬性X和Y,這兩個屬性分別用于記錄橫軸和縱軸的坐標。GestureDetector: 識別各種手勢。 OnGestureListener: 這是一個手勢交互的監聽接口,其中提供了多個抽象方法, 并根據GestureDetector的手勢識別結果調用相對應的方法。

2.GestureListener詳解:

從1中我們知道了監聽手勢的關鍵是:GestureListener 他給我們提供了下述的回調方法:

  • 按下(onDown): 剛剛手指接觸到觸摸屏的那一剎那,就是觸的那一下。
  • 拋擲(onFling): 手指在觸摸屏上迅速移動,并松開的動作。
  • 長按(onLongPress): 手指按在持續一段時間,并且沒有松開。
  • 滾動(onScroll): 手指在觸摸屏上滑動。
  • 按住(onShowPress): 手指按在觸摸屏上,它的時間范圍在按下起效,在長按之前。
  • 抬起(onSingleTapUp):手指離開觸摸屏的那一剎那。

知道了GestureListener的相關方法后,實現手勢檢測也很簡單,步驟如下:

  • Step 1: 創建GestureDetector對象,創建時需實現GestureListener傳入
  • Step 2: 將Activity或者特定組件上的TouchEvent的事件交給GestureDetector處理即可! 我們寫個簡單的代碼來驗證這個流程,即重寫對應的方法:
    代碼如下:
public class MainActivity extends AppCompatActivity {

    private MyGestureListener mgListener;
    private GestureDetector mDetector;
    private final static String TAG = "MyGesture";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //實例化GestureListener與GestureDetector對象
        mgListener = new MyGestureListener();
        mDetector = new GestureDetector(this, mgListener);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mDetector.onTouchEvent(event);
    }

    //自定義一個GestureListener,這個是View類下的,別寫錯哦!!!
    private class MyGestureListener implements GestureDetector.OnGestureListener {

        @Override
        public boolean onDown(MotionEvent motionEvent) {
            Log.d(TAG, "onDown:按下");
            return false;
        }

        @Override
        public void onShowPress(MotionEvent motionEvent) {
            Log.d(TAG, "onShowPress:手指按下一段時間,不過還沒到長按");
        }

        @Override
        public boolean onSingleTapUp(MotionEvent motionEvent) {
            Log.d(TAG, "onSingleTapUp:手指離開屏幕的一瞬間");
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
            Log.d(TAG, "onScroll:在觸摸屏上滑動");
            return false;
        }

        @Override
        public void onLongPress(MotionEvent motionEvent) {
            Log.d(TAG, "onLongPress:長按并且沒有松開");
        }

        @Override
        public boolean onFling(MotionEvent motionEvent, MotionEvent motionEvent1, float v, float v1) {
            Log.d(TAG, "onFling:迅速滑動,并松開");
            return false;
        }
    }

}

對應操作截圖:
1.按下后立即松開:

2.長按后松開:

3.輕輕一滑,同時松開:

4.按住后不放持續做滑動操作:

PS:從上述結果來看,我們發現了一個問題: 我們實現OnGestureListener需要實現所有的手勢,可能我針對的僅僅是滑動,但是你還是要去重載, 這顯得很逗逼,是吧,官方肯定會給出解決方法滴,官方另外給我們提供了一個SimpleOnGestureListener類 只需把上述的OnGestureListener替換成SimpleOnGestureListener即可!

3.簡單的例子:下滑關閉Activity,上滑啟動新的Activity

這里就用上述的SimpleOnGestureListener來實現吧:

運行效果圖:

實現代碼:

public class MainActivity extends AppCompatActivity {

    private GestureDetector mDetector;
    private final static int MIN_MOVE = 200;   //最小距離
    private MyGestureListener mgListener;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //實例化SimpleOnGestureListener與GestureDetector對象
        mgListener = new MyGestureListener();
        mDetector = new GestureDetector(this, mgListener);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return mDetector.onTouchEvent(event);
    }

    //自定義一個GestureListener,這個是View類下的,別寫錯哦!!!
    private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float v, float v1) {
            if(e1.getY() - e2.getY() > MIN_MOVE){
                startActivity(new Intent(MainActivity.this, MainActivity.class));
                Toast.makeText(MainActivity.this, "通過手勢啟動Activity", Toast.LENGTH_SHORT).show();
            }else if(e1.getY() - e2.getY()  < MIN_MOVE){
                finish();
                Toast.makeText(MainActivity.this,"通過手勢關閉Activity",Toast.LENGTH_SHORT).show();
            }
            return true;
        }
    }

}

結果分析: 從上面的對比就可以知道,相比起SimpleOnGestureListener使用SimpleOnGestureListener 顯得更加的簡單,想重寫什么方法就重寫什么方法,另外例子比較簡單,大家可以自己試試 其他玩法,比如通過手勢縮放圖片~

4.手勢添加與識別:

除了上面講解的手勢檢測外,Android還運行我們將手勢進行添加,然后提供了相關的識別API; Android中使用GestureLibrary來代表手勢庫,提供了GestureLibraries工具類來創建手勢庫!

四個加載手勢庫的靜態方法:

獲得GestureLibraries對象后,就可以使用該對象提供的下述方法來做相應操作了:

相關方法:
public void addGesture(String entryName, Gesture gesture):添加一個名為entryName的手勢

public Set<String> getGestureEntries ():獲得手勢庫中所有手勢的名稱

public ArrayList<Gesture> getGestures (String entryName):獲得entryName名稱對應的全部手勢

public ArrayList<Prediction> recognize (Gesture gesture): 從當前手勢庫中識別與gesture匹配的全部手勢
public void removeEntry (String entryName):刪除手勢庫中entryName名稱對應的手勢

public void removeGesture (String entryName, Gesture gesture):刪除手勢庫中entryName和gesture都匹配的手勢

public abstract boolean save ():想手勢庫中添加手勢或從中刪除手勢后調用該方法保存手勢庫

GestureOverlayView手勢編輯組件:
Android為GestureOverlayView提供了三種監聽器接口,如下,一般常用的是:OnGesturePerformedListener;用于手勢完成時提供響應!

5.手勢添加示例:

PS:例子引用的是——李剛《Android瘋狂講義》的代碼

運行效果圖:

好吧,下面貼下實現代碼:

兩個布局文件:activity_main.xml和dialog_save.xml

activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="請在下方屏幕中繪制手勢~"
        android:textSize="20sp"/>


    <!-- gestureStrokeType控制手勢是否需要一筆完成,multiple表示允許多筆-->
    <android.gesture.GestureOverlayView
        android:id="@+id/gesture"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gestureStrokeType="multiple" />

</LinearLayout>

dialog_save.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginRight="8dp"
            android:text="請填寫手勢名稱:"/>
        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/edit_name"/>
    </LinearLayout>

    <ImageView
        android:id="@+id/img_show"
        android:layout_width="128dp"
        android:layout_height="128dp"
        android:layout_marginTop="10dp"/>

</LinearLayout>

MainActivity.java:

public class MainActivity extends AppCompatActivity {

    private EditText editText;
    private GestureOverlayView gesture;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //獲取手勢編輯組件后,設置相關參數
        gesture = (GestureOverlayView) findViewById(R.id.gesture);
        gesture.setGestureColor(Color.GREEN);
        gesture.setGestureStrokeWidth(5);
        gesture.addOnGesturePerformedListener(new GestureOverlayView.OnGesturePerformedListener() {
            @Override
            public void onGesturePerformed(GestureOverlayView gestureOverlayView, final Gesture gesture) {
                View saveDialog = getLayoutInflater().inflate(R.layout.dialog_save,null,false);
                ImageView img_show = (ImageView) saveDialog.findViewById(R.id.img_show);
                final EditText edit_name = (EditText) saveDialog.findViewById(R.id.edit_name);
                Bitmap bitmap = gesture.toBitmap(128,128,10,0xffff0000);
                img_show.setImageBitmap(bitmap);
                new AlertDialog.Builder(MainActivity.this).setView(saveDialog)
                        .setPositiveButton("保存",new DialogInterface.OnClickListener()
                        {
                            @Override
                            public void onClick(DialogInterface dialogInterface, int i) {
                                //獲取文件對應的手勢庫
                                GestureLibrary gestureLib = GestureLibraries.fromFile("/mnt/sdcard/mygestures");
                                gestureLib.addGesture(edit_name.getText().toString(),gesture);
                                gestureLib.save();
                            }
                        }).setNegativeButton("取消", null).show();
            }
        });
    }
}

最后還需要在AndroidManifest.xml中添加寫入SD卡的權限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

6.手勢識別示例

public class MainActivity extends AppCompatActivity {

    private GestureOverlayView gesture;
    private GestureLibrary gestureLibrary;
    private Context mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mContext = MainActivity.this;
        gestureLibrary = GestureLibraries.fromFile("mmt/sdcard/mygestures");
        if (gestureLibrary.load()) {
            Toast.makeText(mContext, "手勢庫加載成功", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(mContext, "手勢庫加載失敗", Toast.LENGTH_SHORT).show();
        }

        //獲取手勢編輯組件后,設置相關參數
        gesture = (GestureOverlayView) findViewById(R.id.gesture);
        gesture.setGestureColor(Color.GREEN);
        gesture.setGestureStrokeWidth(5);
        gesture.addOnGesturePerformedListener(new GestureOverlayView.OnGesturePerformedListener() {
            @Override
            public void onGesturePerformed(GestureOverlayView gestureOverlayView, final Gesture gesture) {
                //識別用戶剛繪制的手勢
                ArrayList<Prediction> predictions = gestureLibrary.recognize(gesture);
                ArrayList<String> result = new ArrayList<String>();
                //遍歷所有找到的Prediction對象
                for (Prediction pred : predictions) {
                    if (pred.score > 2.0) {
                        result.add("與手勢【" + pred.name + "】相似度為" + pred.score);
                    }
                }
                if (result.size() > 0) {
                    ArrayAdapter<Object> adapter = new ArrayAdapter<Object>(mContext,
                            android.R.layout.simple_dropdown_item_1line, result.toArray());
                    new AlertDialog.Builder(mContext).setAdapter(adapter,null).setPositiveButton("確定",null).show();
                }else{
                    Toast.makeText(mContext,"無法找到匹配的手勢!",Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

最后還需要在AndroidManifest.xml中添加寫入SD卡的權限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容