【Android遊戲開發十七】讓玩家自定義手勢玩轉Android遊戲!—Android Gesture之【輸入法手勢技術】

 李華明Himi 原創,轉載務必在明顯處註明:
轉載自 【黑米GameDev街區】 原文連接:  http://www.himigame.com/android-game/340.html

 

不少童鞋說個人代碼運行後,點擊home或者back後會程序異常,若是你也這樣遇到過,那麼你確定沒有仔細讀完Himi的博文,第十九篇Himi專門寫了關於這些錯誤的緣由和解決方法,這裏我在博客都補充說明下,省的童鞋們總疑惑這一塊;請點擊下面聯繫進入閱讀:css

【Android遊戲開發十九】(必看篇)SurfaceView運行機制詳解—剖析Back與Home按鍵及切入後臺等異常處理!html


                                     有童鞋問我爲何不用SDK2.1 ,2.2來進行遊戲開發,那我這裏稍微說兩句:java

1.Android SDK 屬於向下兼容!那麼低版本能夠運行的,高版本基本上更是沒問題!(固然每次SDK的更新也會帶來新功能,或者修改了一些原來的BUG等等,那麼其實對於遊戲開發來講,若是你的遊戲中不須要更高的SDK版本的支持狀況下,徹底沒必要去追求最新的SDK!)android

2.使用低版本進行遊戲開發這樣能兼顧更多的機型,獲取更多的用戶!api

3.你們都知道Android SDK 每次版本的更新,底層代碼也會更健壯和優化了!好比咱們公司的網遊Android版在G2(SDK1.5)上跑起來稍微有些卡,而在個人手機上(SDK2.2)運行起來流暢的沒說的~各類舒坦~~可是這樣也會帶來一些弊端,好比咱們本身遊戲若是上來就用高版本SDK進行開發,那麼對於性能、內存上到底如何,咱們都不會很容易的看出其效果,若是咱們用低版本的SDK則會讓咱們明顯的感覺到性能到底如何~你想一想若是你的遊戲在1.5 ,1.6上跑起來很流暢,那放在更高版本的SDK機器上更是沒說的啦~框架

                    總結:遊戲開發中,若是你遊戲不須要更高的API的支持,那麼推薦基於SDK 1.5和1.6來開發!ide

 

         在上一篇中我給你們介紹了觸摸屏手勢操做,可是這種觸屏手勢的操做比較有侷限性;好比咱們都知道Android能夠利用手勢來解鎖,好比九宮格形式的,經過自定義的一個單筆畫手勢能夠解開屏幕鎖,還能夠自定義筆畫手勢來啓動一個應用等,那麼這種所謂的筆畫手勢其實就是今天我要給你們講解的輸入法手勢識別技術!這種手勢是咱們能夠本身來自定義,而不像以前的觸屏手勢操做只是利用Android 對一些觸屏動做的封裝罷了。下面上幾張手機自定義筆劃手勢解鎖的的截圖:函數

 

          

       左圖中最後一個是自定義解鎖的輸入法手勢~性能

 

OK,那麼既然利用手勢既然能進行解鎖等操做,那麼咱們遊戲開發中,更是能夠加入這一亮點了,好比在遊戲中我畫個圓形執行換背景操做,畫個X表示退出遊戲等等,等等、哈哈 是否是感受頗有意思了?好的,下面就開始進入講解!學習

 

首先本篇主要學習兩點:

     1. 如何建立輸入法手勢、刪除輸入法手勢、從SD卡中讀取出手勢文件!

     2.當輸入法手勢建立後,如何來匹配出咱們的自定義手勢!

 

下面咱們來熟習兩個類和幾個概念:

 

1. 什麼是 GestureOverlayView ?  簡單點說其實就是一個手寫繪圖區;

 

2. 什麼是 GestureLibrary ?   這個類是對手勢進行保存、刪除等操做的,一個存放手勢的小倉庫!

 

3. 筆劃是什麼,字體筆畫?  是的,其實就是跟咱們寫字的筆劃一個概念!

 

4.什麼是筆類型?   輸入法手勢操做中,筆劃類型有兩種;一種是:單一筆劃,另一種是:多筆劃

    所謂單一筆劃筆劃就是一筆劃畫出一個手勢,從你手指接觸屏幕開始到你離開屏幕筆畫就會馬上造成一個手勢!一鼓作氣!

    而多筆劃則是能夠在必定緊湊時間內隨意幾筆劃均可!而後超過這個緊湊時間後便會造成一個手勢!

 

先出項目截圖,簡單說下其功能和操做:

                                   【圖1】                                                                                                                        【圖2】

                        

 

圖1界面中分爲3塊,從上到下依次是:TextView ,EditText,SurfaceView;而後在SurfaceView後面還有一個覆蓋全屏的GestureOverlayView!

圖2界面是在建立好的手勢中匹配手勢的界面,這裏很清晰看出來,找的很對 ~嘿嘿~

 

先看下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="fill_parent"
	android:layout_height="fill_parent">
	<TextView android:id="@+id/himi_tv" android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:text="@string/hello"
		android:textSize="15sp" android:textColor="#FFFFFF00" />
	<EditText android:id="@+id/himi_edit" android:layout_width="fill_parent"
		android:layout_height="wrap_content" />
	<RelativeLayout android:layout_width="fill_parent"
		android:layout_height="wrap_content" android:layout_weight="1">
		<com.himi.MySurfaceView android:id="@+id/view3d"
			android:layout_width="fill_parent" android:layout_height="fill_parent" />
		<android.gesture.GestureOverlayView
			android:id="@+id/himi_gesture" android:layout_width="fill_parent"
			android:layout_height="fill_parent" android:layout_weight="1.0"/>
	</RelativeLayout>
</LinearLayout>

 

xml中註冊的有咱們自定義的surfaceview,對此不太熟悉能夠去看下【Android2D開發之六】,很少解釋了。關於GestureOverlayView這裏也只是簡單的定義了寬高,還有一些重要的屬性設置在代碼中設置了,固然xml也能夠設置的;

 

下面看MainActivity.java

 

**
 *@author Himi
 *@輸入法手勢識別
 *@注意: android.gesture這個類在api-4(SDK1.6)纔開始支持的!
 *@提醒:默認存到SD卡中,因此別忘記在AndroidMainfest.xml加上SD卡讀寫權限!
 */
public class MainActivity extends Activity {
	private GestureOverlayView gov;// 建立一個手寫繪圖區
	private Gesture gesture;// 手寫實例
	private GestureLibrary gestureLib;//建立一個手勢倉庫
	private TextView tv;
	private EditText et;
	private String path;//手勢文件路徑
	private File file;//
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		this.requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.main);
		tv = (TextView) findViewById(R.id.himi_tv);
		et = (EditText) findViewById(R.id.himi_edit);
		gov = (GestureOverlayView) findViewById(R.id.himi_gesture);
		gov.setGestureStrokeType(GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE);//設置筆劃類型 
		// GestureOverlayView.GESTURE_STROKE_TYPE_MULTIPLE 設置支持多筆劃
		// GestureOverlayView.GESTURE_STROKE_TYPE_SINGLE 僅支持單一筆劃
		path = new File(Environment.getExternalStorageDirectory(), "gestures").getAbsolutePath();
		//獲得默認路徑和文件名/sdcard/gestures
		file = new File(path);//實例gestures的文件對象
		gestureLib = GestureLibraries.fromFile(path);//實例手勢倉庫
		gov.addOnGestureListener(new OnGestureListener() { // 這裏是綁定手寫繪圖區
					@Override
					// 如下方法是你剛開始畫手勢的時候觸發
					public void onGestureStarted(GestureOverlayView overlay, MotionEvent event) {
						tv.setText("請您在緊湊的時間內用兩筆劃來完成一個手勢!西西~");
					} 
					@Override
					// 如下方法是當手勢完整造成的時候觸發
					public void onGestureEnded(GestureOverlayView overlay, MotionEvent event) {
						gesture = overlay.getGesture();// 從繪圖區取出造成的手勢
						if (gesture.getStrokesCount() == 2) {//我斷定當用戶用了兩筆劃
							//(強調:若是一開始設置手勢筆畫類型是單一筆畫,那你這裏始終獲得的只是1!)
							if (event.getAction() == MotionEvent.ACTION_UP) {//斷定第兩筆劃離開屏幕
								//if(gesture.getLength()==100){}//這裏是斷定長度達到100像素
								if (et.getText().toString().equals("")) {
									tv.setText("因爲您沒有輸入手勢名稱,so~保存失敗啦~");
								} else {
									tv.setText("正在保存手勢...");
									addGesture(et.getText().toString(), gesture);//我本身寫的添加手勢函數 
								}
							}
						} else {
							tv.setText("請您在緊湊的時間內用兩筆劃來完成一個手勢!西西~");
						}
					} 
					@Override
					public void onGestureCancelled(GestureOverlayView overlay, MotionEvent event) {
					} 
					@Override
					public void onGesture(GestureOverlayView overlay, MotionEvent event) {
					}
				});
		//----這裏是在程序啓動的時候進行遍歷全部手勢!------
		if (!gestureLib.load()) {
			tv.setText("Himi提示:手勢超過9個我作了刪除全部手勢的操做,爲了界面整潔一些!"
					+ " 輸入法手勢練習~(*^__^*)~ 嘻嘻!/n操做介紹:(畫手勢我設置必須畫兩筆劃才行哦~)/n1." +
							"添加手勢:先EditText中輸入名稱,而後在屏幕上畫出手勢!/n2.匹配手勢:" 
					+ "在EditText輸入/"himi/",而後輸入手勢便可! ");
		} else {
			Set<String> set = gestureLib.getGestureEntries();//取出全部手勢
			Object ob[] = set.toArray();
			loadAllGesture(set, ob);
		}
	}
}
 

 

這個就是MainActivity主要代碼了,其中添加手勢、匹配手勢、遍歷手勢、將手勢轉成圖片這些我都單獨寫成了函數,這樣讓各位童鞋更清晰思路一些。

 

從以上代碼中咱們看出在建立手勢以前,手寫繪圖區(GestureOverlayView)確定先被建立出來,而後咱們就能夠在其區域中進行筆劃繪畫手勢了,固然繪畫手勢前,咱們也須要設置了筆劃類型,也就是我一開始給你們介紹的~其後最重要的就是手寫繪圖區的手勢監聽器綁定,增長OnGestureListener這個監聽器重寫了四個函數,這裏最重要的就兩個函數:

      onGestureStarted   和  onGestureEnded  ; 手勢開始和手勢結束的監聽函數!

尤爲是手勢結束監聽這個函數尤其重要,在其中我設置好幾個條件語句,這麼幾個條件一方面是讓你們瞭解Gesture中一些比較重要經常使用的方法,另外一方面我要提醒各位童鞋:

若是你設置筆劃類型是多筆劃類型的,那麼理想狀態下,應該是在一段緊湊時間內,無論你使用了幾筆划來繪製手勢,系統都應該在斷定你在必定短暫時間內沒有再進行筆劃的時候才應該建立手勢,而且系統響應此函數;

      其實錯了,一開始我也這麼想,可是發現,無論你設置的筆劃類型是單一的仍是多筆劃當你手指離開屏幕,無論你當前是第幾筆,Android都會去響應這個完成函數,so~ 我在這裏調用手勢Gesture類中的getStrokesCount()函數,這個函數會記錄在緊湊時間內你繪製手勢的筆劃數,那麼根據這個函數咱們就能夠解決手指離開屏幕總被響應的問題了,由於單一筆劃類型永遠這個值不會大於1!

而 if (event.getAction() == MotionEvent.ACTION_UP) {}寫這個只是給你們演示第二個參數按鍵動做該怎麼用;

 

那麼咱們下面就來看如何建立一個手勢:

 

public void addMyGesture(String name, Gesture gesture) { 
		try {
			if (name.equals("himi")) {
				findGesture(gesture);
			} else {
				// 關於兩種方式建立模擬器的SDcard在【Android2D遊戲開發之十】有詳解
				if (Environment.getExternalStorageState() != null) {// 這個方法在試探終端是否有sdcard!
					if (!file.exists()) {// 斷定是否已經存在手勢文件
						// 不存在文件的時候咱們去直接把咱們的手勢文件存入
						gestureLib.addGesture(name, gesture);
						if (gestureLib.save()) {////保存到文件中
							gov.clear(true);//清除筆畫
							// 注意保存的路徑默認是/sdcard/gesture ,so~別忘記AndroidMainfest.xml加上讀寫權限!
							// 這裏抱怨一下,咳咳、其實昨天就應該出這篇博文的,就是由於這裏老是異常,今天仔細看了
							// 才發現不是沒寫權限,而是我雖然在AndroidMainfest.xml中寫了權限,可是寫錯了位置..哭死!
							tv.setText("保存手勢成功!由於不存在手勢文件," + "因此第一次保存手勢成功會默認先創" +
									"建了一個手勢文件!而後將手勢保存到文件中.");
							et.setText("");
							gestureToImage(gesture);
						} else {
							tv.setText("保存手勢失敗!");
						}
					} else {//當存在此文件的時候咱們須要先刪除此手勢而後把新的手勢放上
						//讀取已經存在的文件,獲得文件中的全部手勢
						if (!gestureLib.load()) {//若是讀取失敗
							tv.setText("手勢文件讀取失敗!");
						} else {//讀取成功 
							Set<String> set = gestureLib.getGestureEntries();//取出全部手勢
							Object ob[] = set.toArray();
							boolean isHavedGesture = false;
							for (int i = 0; i < ob.length; i++) {//這裏是遍歷全部手勢的name 
								if (((String) ob[i]).equals(name)) {//和咱們新添的手勢name作對比
									isHavedGesture = true;
								}
							}
							if (isHavedGesture) {//若是此變量爲true說明有相同name的手勢
//----備註1-------------------//gestureLib.removeGesture(name, gesture);//刪除與當前名字相同的手勢
/*----備註2-----------------*/gestureLib.removeEntry(name);
								gestureLib.addGesture(name, gesture);
							} else {
								gestureLib.addGesture(name, gesture);
							}
							if (gestureLib.save()) {
								gov.clear(true);//清除筆畫 
								gestureToImage(gesture);
								tv.setText("保存手勢成功!當前全部手勢一共有:" + ob.length + "個");
								et.setText("");
							} else {
								tv.setText("保存手勢失敗!");
							}
							////------- --如下代碼是當手勢超過9個就所有清空 操做--------
							if (ob.length > 9) {
								for (int i = 0; i < ob.length; i++) {//這裏是遍歷刪除手勢
									gestureLib.removeEntry((String) ob[i]);
								}
								gestureLib.save();
								if (MySurfaceView.vec_bmp != null) {
									MySurfaceView.vec_bmp.removeAllElements();//刪除放置手勢圖的容器
								}
								tv.setText("手勢超過9個,已所有清空!");
								et.setText("");
							}
							ob = null;
							set = null;
						}
					}
				} else {
					tv.setText("當前模擬器沒有SD卡 - -。");
				}
			}
		} catch (Exception e) {
			tv.setText("操做異常!");
		}
	}
 

這裏也都很好理解,套路相似以前File文件存儲的套路,先判斷SD是否存在,而後是文件是否存在:

若是文件不存在就先直接添加到手勢到手勢倉庫中,而後手勢倉調用gestureLib.save()纔算把手勢存到SD卡的手勢文件中。 

文件存在的話還要去斷定是否文件中包含了相同名字的手勢;固然這裏能夠不斷定是否有相同手勢名存在,而後進行刪除操做!其實也可不刪除,直接添加進去當前新建的手勢;緣由看了下面的備註解釋就明白了;

 

 

備註 1:由於gestureLib保存的手勢是個HashMap, key=手勢的名字,value=手勢,因此gestureLib.removeGesture(name, gesture);這種刪除方式只是刪除了手勢,該手勢名字依舊保存在hashmap中,下次還有相同的name手勢存入的時候Hashmap就直接覆蓋本條目了。因此根據Hashmap的特徵,咱們能夠不進行刪除操做,能夠直接gestureLib.addGesture(name, gesture);由於若是出現相同的手勢名字的手勢,Hashmap就會根據key(手勢的名字)直接覆蓋其條目的value(手勢)滴~

備註2 :這裏也是一種刪除手勢的方式,可是這種方式跟備註1的不一樣,這裏是將Hashmap中的條目刪除,也就是說key和value都被刪去!

 

下面看下如何把手勢轉成bitmap!

public void gestureToImage(Gesture ges) {//將手勢轉換成Bitmap
		//把手勢轉成圖片,存到咱們SurfaceView中定義的Image容器中,而後都畫出來~
		if (MySurfaceView.vec_bmp != null) {
			MySurfaceView.vec_bmp.addElement(ges.toBitmap(100, 100, 12, Color.GREEN));
		}
	}
 

 

 

下面是如何遍歷手勢!

public void loadAllGesture(Set<String> set, Object ob[]) { //遍歷全部的手勢 
		if (gestureLib.load()) {//讀取最新的手勢文件
			set = gestureLib.getGestureEntries();//取出全部手勢
			ob = set.toArray();
			for (int i = 0; i < ob.length; i++) {
				//把手勢轉成Bitmap
				gestureToImage(gestureLib.getGestures((String) ob[i]).get(0));
				//這裏是把咱們每一個手勢的名字也保存下來
				MySurfaceView.vec_string.addElement((String) ob[i]);
			}
		}
	}
 

 

下面最後來看看手勢的匹配!(超重要的!本身也搞了許久才找到解決的方法)

 

public void findGesture(Gesture gesture) {
		try {
			// 關於兩種方式建立模擬器的SDcard在【Android2D遊戲開發之十】有詳解
			if (Environment.getExternalStorageState() != null) {// 這個方法在試探終端是否有sdcard!
				if (!file.exists()) {// 斷定是否已經存在手勢文件
					tv.setText("匹配手勢失敗,由於手勢文件不存在!!");
				} else {//當存在此文件的時候咱們須要先刪除此手勢而後把新的手勢放上
					//讀取已經存在的文件,獲得文件中的全部手勢
					if (!gestureLib.load()) {//若是讀取失敗
						tv.setText("匹配手勢失敗,手勢文件讀取失敗!");
					} else {//讀取成功 
						List<Prediction> predictions = gestureLib.recognize(gesture);
						//recognize()的返回結果是一個prediction集合,
						//包含了全部與gesture相匹配的結果。
						//從手勢庫中查詢匹配的內容,匹配的結果可能包括多個類似的結果, 
						if (!predictions.isEmpty()) {
							Prediction prediction = predictions.get(0);
							//prediction的score屬性表明了與手勢的類似程度
							//prediction的name表明手勢對應的名稱 
							//prediction的score屬性表明了與gesture得類似程度(一般狀況下不考慮score小於1的結果)。 
							if (prediction.score >= 1) {
								tv.setText("當前你的手勢在手勢庫中找到最類似的手勢:name =" + prediction.name);
							}
						}
					}
				}
			} else {
				tv.setText("匹配手勢失敗,,當前模擬器沒有SD卡 - -。");
			}
		} catch (Exception e) {
			e.printStackTrace();
			tv.setText("因爲出現異常,匹配手勢失敗啦~");
		}
	}
 

 

 

那麼最後給各位童鞋說一下,其實輸入法手勢操做非常適合遊戲中使用,不論是觸摸屏手勢操做仍是今天講的輸入法手勢操做若是加到遊戲中那都是至關讚的!可是咱們公司網遊引擎和框架不適合插入手勢 - -、唉~


其實前兩天應該發這篇的,可是由於工做忙了幾天,讓你們久等了,挺很差意思的,因此今天熬夜給你們寫了出來,如今都凌晨 7:00 了~

 

源碼下載地址: http://www.himigame.com/android-game/340.html

 

 

 

.


原文連接: http://blog.csdn.net/xiaominghimi/article/details/6137136
相關文章
相關標籤/搜索