[Mooc][Android]Programming Mobile Aplication on Android Platform(week8) - Data Management

image

前言:

最後一週,分成兩個一個是Assignment.另外還有Peer Assignment,要努力堅持到最後.

筆記:

關於資料管理的部分:

  • SharePreferences 屬於小量的資料存取,(iOS 使用的是NSUserDefault 與 pList來管理)
    • 讀取資料getInt getString
    • 寫入資料必須先取得SharedPreferences.Editor 之後再setInt or setString
  • PreferenceFragment 是一個UI Fragment可以存取與直接收到SharedPreferences 的變化.
    • 透過setContentView可以把一個表示資料與介面的xml檔案設定好,然後存取它所代表的資料.
  • File 可以使用 internal memory 或是external memory
    • 其中比較需要注意的是MODE_PRIVATE會把檔案鎖定成這個App才能讀取或是只用同樣的user ID跟App.
    • 對於external storage需要注意以下的事項:
      • 對於external memory 的檔案需要在manifest上面增加相關的權限:
      • 存取前要先注意是否有讀或寫的權限:
        • Environment .getExternalStorageState()
  • SQL (SQLlite)
    • 如果要繼承SQLiteOpenHelper來使用,必須要Override OnCreate跟OnUpgrade.
    • 資料庫的檔案在 /data/data//databases/
    • 透過以下方式可以讀取SQL資料
      • adb -s emulator-5554 shell
      • sqlite3 /data/data
// SharedPreferences

final SharedPreferences prefs = getPreferences(MODE_PRIVATE);
//get data from SharedPreferences
int high_score = prefs.getInt("high_score", 0);

//set data to SharedPreferences
SharedPreferences.Editor editor = prefs.edit();
editor.putInt("high_score", 99);
editor.commit();

//File read sample (internal storage)
FileInputStream fis = openFileInput(fileName);
BufferedReader br = new BufferedReader(new InputStreamReader(fis));

String line = "";

while (null != (line = br.readLine())) {

	TextView tv = new TextView(this);
	tv.setTextSize(24);
	tv.setText(line);

	ll.addView(tv);

}
br.close();

//File write
FileOutputStream fos = openFileOutput(fileName, MODE_PRIVATE);

PrintWriter pw = new PrintWriter(new BufferedWriter(
		new OutputStreamWriter(fos)));

pw.println("Line 1: This is a test of the File Writing API");
pw.println("Line 2: This is a test of the File Writing API");
pw.println("Line 3: This is a test of the File Writing API");

pw.close();


//File read sample (external storage)
if (Environment.MEDIA_MOUNTED.equals(Environment
		.getExternalStorageState())) {

	File outFile = new File(
			getExternalFilesDir(Environment.DIRECTORY_PICTURES),
			fileName);
	
	if (!outFile.exists())
		copyImageToMemory(outFile);
	
	ImageView imageview = (ImageView) findViewById(R.id.image);
	imageview.setImageURI(Uri.parse("file://" + outFile.getAbsolutePath()));
		}

關於內容提供者的部分(Content Provider)

  • Content Provider提供可以存取有結構的資料,可以跨app來存取.
  • Content Resolver可以讓你去存取content provider裡面的資料.其中透過URI來傳遞與定位資料的相關資訊.
// Sample to using ContentResolver and URI
// Contact data
String columnsToExtract[] = new String[] { Contacts._ID,
		Contacts.DISPLAY_NAME, Contacts.PHOTO_THUMBNAIL_URI };

// Get the ContentResolver
ContentResolver contentResolver = getContentResolver();

// filter contacts with empty names
String whereClause = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
		+ Contacts.DISPLAY_NAME + " != '' ) AND (" + Contacts.STARRED
		+ "== 1))";

// sort by increasing ID
String sortOrder = Contacts._ID + " ASC";

// query contacts ContentProvider
Cursor cursor = contentResolver.query(Contacts.CONTENT_URI,
		columnsToExtract, whereClause, null, sortOrder);

// pass cursor to custom list adapter
setListAdapter(new ContactInfoListAdapter(this, R.layout.list_item,
		cursor, 0));
  • 其中要利用content provider去變更你Google的通訊錄的範例需要注意以下項目:
    • 在manifest裡面,取得
    • 透過Google Account Manager取得帳戶資訊來讀取資料.
    • 透過ContentProviderOperation 來新增一個系列的batch執行序列(batch operation)
    • 透過content resolver來執行執行序列 (getContentResolver().applyBatch())
// Get Account information
// Must have a Google account set up on your device
String mAccountList = AccountManager.get(this).getAccountsByType("com.google");
String mType = mAccountList[0].type;
String mName = mAccountList[0].name;

// Set up a batch operation on Contacts ContentProvider
ArrayList batchOperation = new ArrayList();

for (String name : mNames) {
int position = ops.size();

// First part of operation
batchOperation(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
		.withValue(RawContacts.ACCOUNT_TYPE, mType)
		.withValue(RawContacts.ACCOUNT_NAME, mName)
		.withValue(Contacts.STARRED, 1).build());

// Second part of operation
batchOperation(ContentProviderOperation.newInsert(Data.CONTENT_URI)
		.withValueBackReference(Data.RAW_CONTACT_ID, position)
		.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
		.withValue(StructuredName.DISPLAY_NAME, name).build());
}

try {

	// Apply all batched operations
	getContentResolver().applyBatch(ContactsContract.AUTHORITY,
			batchOperation);

} catch (RemoteException e) {
	Log.i(TAG, "RemoteException");
} catch (OperationApplicationException e) {
	Log.i(TAG, "RemoteException");
}
</pre>

- 剛剛關於content provider的範例都是使用已經架設的好的content provider.如果要新增自己的個人的content provider需要透過以下的流程:
    - 新增相關權限在 mannifest裡面: 新增 < provider /> 在< application /> 內
    - 建立 ContentProvider 衍生類別
    - 設定好唯一的URI之後,就可以透過這個URI使用ContentResolver來取得資訊.


關於Service的部分:

- 關於Service類別:
    - 沒有使用者介面
    - 兩個主要用途
        - 可以執行背景程序     
        - 跨App間的互動部分
- 關於IntentService
    - 將Service像是Intent一樣使用,建立後會在App背景等待呼叫
    - 透過Intent 來設定,但是透過startService而不是startActivity來啟動
- 關於與遠端的service溝通方式:
    - 使用messenger
        - 透過messanger來與service傳遞與接受訊息
    - 使用AIDL (Android Interface Definition Language)
        - 先宣告interface在 .aidl file
        - 不論是service與user都需要有建立 .aidl 檔案
        - 在Server端:
            - 需要建立與stub相關的API
        - 在User端:
            - 在ServiceConnection取得Service stub本體
            - 在App使用stub本身去呼叫他的API
            - 結束的時候必須要使用onServiceDisconnected清除stub    
    - Android team有建議要小心使用AIDL如果是在multiple thread的部分下面,或許可以考慮使用Messenger[http://developer.android.com/guide/components/bound-services.html](http://developer.android.com/guide/components/bound-services.html)            


// Intent Service sampl
public class LoggingService extends IntentService {

	public static String EXTRA_LOG = "course.examples.Services.Logging.MESSAGE";
	private static final String TAG = "LoggingService";

	public LoggingService() {
		super(TAG);
	}

	@Override
	protected void onHandleIntent(Intent intent) {

		Log.i(TAG, intent.getStringExtra(EXTRA_LOG));

	}
}


// 設定Intent
Intent startServiceIntent = new Intent(getApplicationContext(),
		LoggingService.class);

// 把需要log的資訊透過 putExtra 傳給Intent
startServiceIntent.putExtra(LoggingService.EXTRA_LOG,
		"Log this message");

// 啟動service
startService(startServiceIntent);

**關於作業:(2014/11/22更新)** - Cursor 做任何讀取的時候,需要知道他是不是empty - Cursor.moveToFirst 這時候才會傳回 cursor是不是 empty 直接檢查指標是不是null 是沒有用的 - 參考: [http://developer.android.com/reference/android/database/Cursor.html](http://developer.android.com/reference/android/database/Cursor.html) - 這次官方給的作業有bug,需要改manifest才會出現 action bar menu - 修改: - <activity android:theme="@style/Theme.AppCompat.Light" ... > 在Manifest - 這個讓我找了好久..... - 參考: [http://developer.android.com/guide/topics/ui/actionbar.html](http://developer.android.com/guide/topics/ui/actionbar.html) - 其實最困難的部分就是swapcursor的部分(笑),我想是因為註解太多敘述了,其實就專心地把swapcursor該做的做就好.陷阱題....... **參考網址:** - Android Storage Option(包含 SharedPreferences,Internal Storage,External Storage跟SQl) - [http://developer.android.com/guide/topics/data/data-storage.html](http://developer.android.com/guide/topics/data/data-storage.html) - Android about bound services - [http://developer.android.com/guide/components/bound-services.html](http://developer.android.com/guide/components/bound-services.html) - Android how to use cursor - [http://developer.android.com/reference/android/database/Cursor.html](http://developer.android.com/reference/android/database/Cursor.html) - Android about action bar menu - [http://developer.android.com/guide/topics/ui/actionbar.html](http://developer.android.com/guide/topics/ui/actionbar.html)

[Android學習]心血來潮把Android SDK更新(ADT 23)所帶來的問題與解決

image

前言:

週末前剛好心血來潮更新了Android SDK,也看到了ADT有了23版,並且當然也有下載最新的Android L 的SDK.想不到下載完之後造成Eclipse完全無法使用,利用了週末的時間好好的把系統整理了一次.(決定重灌,並且也要重新安裝gradle)

接下來就記錄一下,那些部分有遇到問題.希望能幫助到一樣遇到問題的你.以下這篇文章有更清楚地解釋出現了22.6更換到23版的錯誤

錯誤與修復:

image

  • 有關更新到Android SDK 23的錯誤
    • 錯誤狀況:
      • “This Android SDK requires Android Developer Toolkit version 23.0.0 …“並且重開也沒有效過.當然Check for update 也沒出現任何可以更新
    • 解決方法:
      • 根據一些網友的討論,似乎在[Install New Software]-> update SDK to 23.0
      • 但是我都會出現更新的dependency 出錯,根據這篇文章似乎從2014的六月到現在都還沒解決.所以我還是決定重灌Android Developer Toolkit
  • 有關更新到ADT 23之後,ADB 無法順利的執行(如果你有使用Genymotion 23.1之前的版本的話)
    • 錯誤狀況:
      • Genymotion 模擬器執行之後,Eclipse一直無法正確的編譯程式.出現 “The connection to adb is down, and a severe error has occured”
      • 到 ANDROID_SDK/sdk/platform-tools/adb 去跑 ./adb kill-server 與 ./adb start-server 也無法成功.
    • 解決方法:
      • 這邊有兩個解決方法,一個是把你的Genymotion更新到23.1
      • 另外一個就是依照以下這篇文章去改變genymotion的設定不使用自己的Adb而使用Android SDK的adb

參考資料:

[Mooc][Python][Week8]終於要完成了..... 完成最後的太空船射擊遊戲

image

前言與心得:

前一次的作業雖然說是要做一個太空船射擊隕石的遊戲,但是僅僅是完成前半段.也就是只有完成船的移動,發射飛彈與隕石的移動部分.接下來就要做碰撞跟多個隕石的控制.

總算把這個課程弄完了,最後的作業其實讓我寫起來很過癮.這大概也是為什麼我除了平常晚上努力寫之外.週末還要拿時間來寫. 主要是最由透過去寫一個遊戲,來完成對於物件導向與多個物件的管理.

比如說,你要如何管理一個子彈飛行的時間跟是否有撞擊到目標.其實只要管理好一個,到多個的時候自然水到渠成.一步步慢慢地完成每個單獨的類別,然後看到多個物件瞬間可以完成的成就感真不錯.

筆記:

這一個章節主要是講解 List,Set與 Dictionary.而重心主要在Set上面.

  • 關於List
    • 資料可以重複
  • 關於Set
    • 不會重複
    • 在逐一讀取(iteration)的時候,不應該做新增跟移除的動作,會造成索引混亂(也就是會讀錯個或是少讀)
      • 一般的作法就是另外列出一個集合後,利用差集合(difference_update)去運算.
      • 其中要注意difference不會改變原來的集合元素,而difference_update會.
    • 其中對於原來集合會有變更mutate的函式,有以下:
      • add/remove/modify/update(但是s.update(s)不會更改)
      • 兩個集合操作要有_update的函式
def get_rid_of(inst_set, starting_letter):
    #透過另外一個集合來搜集所有要刪除的
    remove_set = set([])
    for inst in inst_set:
        if inst[0] == starting_letter:
            remove_set.add(inst)
    #透過difference_update來移出所有在remove_set裡面的元素            
    inst_set.difference_update(remove_set)

作業:

其實上次不小心好像把這個禮拜的作業都弄完了.不過還是有一些細節需要微調:

  • 由於這是一個太空船射擊遊戲,主要就是一個飛船在螢幕上可以發射飛彈來射擊每秒隨機產生的隕石.
    • 所以原先的做法是寫了許多的函式來處理撞擊與子彈飛行的部分,這先部分要拆解到原先飛船與隕石各自的類別來處理.
    • 對於個別物件的參數取得,原本做法比較粗糙就是直接取得.這部分得都改由函式來取得.
  • 整個架構上,出題的人希望你透過兩個類別就能完成整個遊戲.一個類別是飛船,另外一個則是sprite.而不論是隕石,子彈或是爆炸三者都被視為是sprite的一個部分.
    • 其中就必須要區分出來這三者哪些有生命週期,比如子彈跟爆炸必須要多久之後消失,而隕石不需要.
    • 哪些屬於動畫需要改變畫圖的image

[Mooc][Android]Programming Mobile Aplication on Android Platform(week7) - Sensor/Location/Map

image

前言:

最近開始比較忙碌了,也希望趕快把兩個課程結束.不然每次都得半夜熬夜想作業.尤其Python作業還搞到週末才弄完.

筆記:

  • 關於Sensor的部分:
    • 有各種的Sensor可以使用(動態(motion),動作(action),還有環境(environment)).
    • 使用:
      • 透過 getSystemService(context.SENSOR_SERVICE)
      • 夠過註冊onSensorChanged() 與 onAccuracyChanged()來接收變更.
      • 如果發現精準度(Accuracy)增加了,對於這樣得資料改變需要處理
      • 關於座標系統(Coordinate System):
        • X: 左到右
        • Y: 下到上
        • Z: 前面到後面
        • 手機的橫放(landscape)或是直放(potrait)不會影嚮座標系統
      • 關於動態資訊與磁力座標(magnetic)資訊處理:
        • getRotationMatrix: 先透過手機的資訊轉換實際世界的資訊,主要是往北方的資訊
        • getOrientation: 拿到手機orientation資訊,裡面資訊是弧度(radians)
        • 轉換弧度(radians)到角度(degree)
@Override
	public void onSensorChanged(SensorEvent event) {

		// Acquire accelerometer event data
		
		if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

			mGravity = new float[3];
			System.arraycopy(event.values, 0, mGravity, 0, 3);

		} 
		
		// Acquire magnetometer event data
		
		else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {

			mGeomagnetic = new float[3];
			System.arraycopy(event.values, 0, mGeomagnetic, 0, 3);

		}

		// If we have readings from both sensors then
		// use the readings to compute the device's orientation
		// and then update the display.

		if (mGravity != null && mGeomagnetic != null) {

			float rotationMatrix[] = new float[9];

			// Users the accelerometer and magnetometer readings
			// to compute the device's rotation with respect to
			// a real world coordinate system

			boolean success = SensorManager.getRotationMatrix(rotationMatrix,
					null, mGravity, mGeomagnetic);

			if (success) {

				float orientationMatrix[] = new float[3];

				// Returns the device's orientation given
				// the rotationMatrix

				SensorManager.getOrientation(rotationMatrix, orientationMatrix);

				// Get the rotation, measured in radians, around the Z-axis
				// Note: This assumes the device is held flat and parallel
				// to the ground

				float rotationInRadians = orientationMatrix[0];

				// Convert from radians to degrees
				mRotationInDegress = Math.toDegrees(rotationInRadians);            
  • 關於地理位置(location)方面
    • 需要先邀請使用者提供以下兩種權限:
      • ACCESS_COARSE_LOCATION:
        • 提供網路所提供的地理資訊(較不精確,省電)
      • ACCESS_FINE_LOCATION
        • 提供GPS資訊以及網路所綜合出比較精確的資訊(精確,較耗電)
    • GPS 提供者:
      • GPS sensor(比較準,但是比較慢)
      • 網路提供(比較快,但是比較不準)
      • Passive (不會一直可以使用)
    • 關於位置(location)的判斷程序:
      • 先判斷是否上一次判斷結果是不是最佳的位置 ( 根據位置與精確度),其中精確度是不斷會改變的.
      • 透過精確度來判斷現在的地理資訊是不是最佳的位置.
      • 更新最佳的地理資訊
  • 關於地圖方面:
    • 之前已經玩過Google Map API所以這裡不再提了…

關於作業:

  • 關於虛擬位置選項的開啟
    • 由於這次是要撰寫取得位置的App,程式中有使用虛擬位置(mockLocation),一開始我發現這個部分會一直導致我的App Crash但是一直找不到原因(我使用Galaxy Nexus).(就算什麼都不加也一樣),最後才發現跟選項有關
    • 方法:
      • 實體手機選項 [設定]->[開發者人員選項]->[允許模擬位置] 打勾
      • 我發現使用[GenyMotion]裡面來模擬Galaxy Nexus 也是一樣,不啟動這個部分會有crash的錯誤.
      • 教學文章
      • 還是聽不懂或是找不到嗎? 這裏有影片教你

參考資料:

[Mooc][Python][Week7] 太空船射擊遊戲

image

前言:

自從課程開始忙碌之後,我已經把Android跟Python的順序開始做個調整.不過因為這堂課程的loading一直不算輕鬆,雖然他的主要課程內容圍繞著他所提供的simplegui與codeskulptor之外,他的作業其實都是屬於peer assignment.也就是說你除了寫好作業之外,你還需要改其他同學的作業,不然就像我這個禮拜一樣被扣了20%的分數 orz .

課程筆記:

  • 這次主要是圍繞著如何利用聲音播放與一些OnDraw的相關應用去做出一個太空船射擊的遊戲.這邊其中就會牽扯到一些部分需要瞭解:
    • 關於推動力與摩擦力的運算
    • 關於聲音的播放
    • 對於動態物件的表示,與封裝.比如說隕石跟太空船都是會動的物件,但是隕石會飛出畫面外,太空船則不會.
    • 對於畫面上物件的碰撞計算,前幾天在Android的作業裡面也是一樣的東西.
  • 利用Dictionary 達成variable point 或是 function pointer map.
    • 裡面的programming tip是每個禮拜課程中,都會教導一下關於這個禮拜可能會遇到的一些小技巧與需要注意的地方.
    • 這次提到透過dictionary 裡面去加入其他函示的方式來達成function map的作用.當然舉一反三也可以指向其他的類別
#關於其他function point mapping
def f():
    pass

d = { 0 : f }
d[0]()   # call f function.    



#利用Dictionary 做到類別mapping
class C1:
    def __init__(self):
        self.inVal =  5
        
    def get(self):        
        return self.inVal
    
obj1 = C1()
print str(obj1.get()) #5

d = { 0:C1 }

obj2 = d[0]()
print str(obj2.get()) #5

作業筆記:

作業不簡單,所以寫了一下子才能回來更新部落格.一些筆記如下,大部分與http://www.codeskulptor.org裡面的simplegui處理有關:

  • 關於圖片顯示,原來的圖片都是PNG所以已經能透明,只要需要把圖片換成網路上png自行會處理透明的部分
  • 先試著處理keyboard event 對應著圖片旋轉的部分,這時候會發現圖片預設是0度(degree),所以要處理一下.
  • 要寫這個作業,首先要先來複習一下角度(degree)與弧度(radians).這邊我弄了很久因為我把角度(degree)的起始點弄錯,所自然算出來的弧度(radians)就是錯的.

image


#算出推進力,課堂的影片有提到
forward = (cos(radians) ,  sin(radians) )

參考資料:

[Mooc][Android]Programming Mobile Aplication on Android Platform(week6) - Graphic/Multiple Touch/Multiple Media

image

前言:

到了第六週,也只剩下三週,要好好努力了. 感覺這個禮拜的內容有點多,看來得仔細把筆記做一下,也希望都能熟悉各個元件的用法.

筆記:

  • Graphic 有兩種畫的方式:
    • Draw on canvas: 比較複雜,需要變動的繪圖.
    • Draw on view: 比較簡單,不需要變動的部分.
  • Draw on View:
    • 可以有兩種方式來達成,將所有需要的外觀用xml來敘述,或是寫在程式碼裡面的MainActivity的OnCreate也是可以.
    • xml 可以是引用其他的xml,比如說顏色敘述可以是另外一個xml
// In  main.xml
        android:background="@drawable/sq2"

// In drawable/sq2.xml
< ?xml version="1.0" encoding="utf-8"?>
< shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >
    < solid android:color="#FFFFFF00" />
    < stroke
        android:width="25dp"
        android:color="#FFFF0000" />
< /shape>
  • Draw on Canvas:
    • 透過View 來達成: (比較少更新)
      • 透過Canvas 提供的View 來使用
      • 透過 OnDraw 來更新畫面
    • 透過SurfaceView 來達成: (需要常更新)
      • 具有比較好的效能來更新UI
      • 不是屬於UI Drawing Thread來更新畫面.
      • 更新方式: (透過Surface callback)
        • surfaceCreated()
        • surfaceChanged()
        • sufaceDestroyed()
      • 使用流程:
        • SufaceHolder.lockCanvas() 先把UI resource 鎖定
        • 透過類似CCanvas.drawBitmap 來更新畫面
        • SufaceHolder.unlockCanvasAndPost() 來歸還UI resource
  • View Animation:
    • TransitionDrawable:
      • 用於兩個Drawable資源的切換,不過僅支援fade in/out的過場特效
    • Animation:
      • 可以做許多不同的Animation,比如說Alpha 變換(也就是fade in/out) 或是 rotate , translate與 scale(大小).
    • ValueAnimator:
      • 可以透過onAnimationUpdate callback 來調整每個animation 要改變的數值
    • ViewPropertyAnimator:
      • 每個view本身都有一個 .animate() 的ViewPropertyAnimator可以取用
      • 可以透過 Runnable去串接下一個Runnable來達成連續Animation的效果
//  TransitionDrawable 
Drawable[] layers = new Drawable[2]; //Two drawable in list
layers[0] = new ColorDrawable(Color.TRANSPARENT);
layers[1] = new BitmapDrawable(bitmap);
TransitionDrawable drawable = new TransitionDrawable(layers);

image.setImageDrawable(drawable);

drawable.startTransition(300);

 
// Animation java code
    // Create animation
    mAnim = AnimationUtils.loadAnimation(this, R.anim.view_animation);
    // Set Animation in ImageView when get focus
    mImageView.startAnimation(mAnim);


// Animation xml
    < alpha   //fade in/out
        android:duration="3000"
        android:fromAlpha="0.0"
        android:interpolator="@android:anim/linear_interpolator"
        android:toAlpha="1.0" />

    < rotate //rotate
        android:duration="4000"
        android:fromDegrees="0"
        android:interpolator="@android:anim/accelerate_interpolator"
        android:pivotX="50%"
        android:pivotY="50%"
        android:startOffset="3000"
        android:toDegrees="720" />

// View PropertyAnimation
    fadeIn.run();  // Run first runnable

	Runnable fadeIn = new Runnable() {
		public void run() {
			mImageView.animate().setDuration(3000)
					.setInterpolator(new LinearInterpolator()).alpha(1.0f)
					.withEndAction(rotate);
					//Call next runnable rotate.
		}
	};

  • Multiple Touch Event:
    • OnTouch event handle MotionEvent:
      • Pointer ID: 代表著每一個觸碰的點.當有multiple touch發生的時候,Pointer ID 就有複數個.
      • MotionEvent.ACTION_MOVE.ACTION_POINTER_UP: 也是可能有多個pointer ID依序傳入.
      • MotionEvent.ACTION_MOVE: 是一次傳入一個群組的 id,你需要一個個去拿來處理.
    • OnTouch event handle by GestureDetector:
      • GestureDetector可以幫你判別是何種手勢,並且依據支援的手勢來呼叫
      • 如果需要增加新的手勢,需要使用GestureBuilder並且把檔案複製到App
        • 檔案從 /mnt/sdcard/gestures
        • 到 /res/raw 目錄夾
    • 客製化手勢使用流程:
      • 先使用 GesutureLibraries 把客製化的手勢讀進來
      • 輸入新的手勢之後,會得到一個預測的手勢陣列.取出分數最高的來處理
/*
 *  Multiple touch handle by MotionEvent
 */
 
// Init frame layout object 
FrameLayout mFrame = (FrameLayout) findViewById(R.id.frame);

// Create and set on touch listener
mFrame.setOnTouchListener(new OnTouchListener() {

	@Override
	public boolean onTouch(View v, MotionEvent event) {

		switch (event.getActionMasked()) {

		// Show new MarkerView
		
		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_POINTER_DOWN: {

			int pointerIndex = event.getActionIndex();
			int pointerID = event.getPointerId(pointerIndex);
            // Each pointerID represent one finger movement. ex: 2 fingers will call twice in this function.
            
            ...  // Do something about handle in each finger point down.
            
			break;
		}


        // Handle move as group.
        case MotionEvent.ACTION_MOVE: {
        
        	for (int idx = 0; idx < event.getPointerCount(); idx++) {
        
        		int ID = event.getPointerId(idx);
        
        		MarkerView marker = mActiveMarkers.get(ID);
        		if (null != marker) {
        			// Do something with marker
        			}
        		}
        	}
        
        	break;
        }


/*
* Multiple touch handle by Gestture Detector and Custom Gestures
*/

// Load custom gestures
    GestureLibrary mLibrary = GestureLibraries.fromRawResource(this, R.raw.gestures);
    if (!mLibrary.load()) {
    	finish();
    }
	public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {

		// Get gesture predictions
		ArrayList predictions = mLibrary.recognize(gesture);

		// Get highest-ranked prediction
		if (predictions.size() > 0) {
			Prediction prediction = predictions.get(0);

			// Ignore weak predictions

			if (prediction.score > 2.0) { //limited prediction score must higher 2.0
			if (prediction.name.equals("CUSTOM_GESTURE_ONE")) {
			        // Do something.

				}
			}

</pre>

- Multiple Media Player
    - 啟動MediaPlayer流程:
        - setDataSource()
        - prepare()
        - start() 
    - 啟動MediaRecorder流程 (以錄音為例):
        - setAudioSource(MediaRecorder.AudioSource.MIC)
        - setOutputFormat(MediaRecorder.OutputFormat.XXX)
        - setOutputFile()
        - setAudioEncoder(MediaRecorder.AudioEncoder.XXX)
        - prepare()
        - start()       

// Start recording with MediaRecorder
private void startRecording() {

	mRecorder = new MediaRecorder();
	mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
	mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
	mRecorder.setOutputFile(mFileName);
	mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);

	try {
		mRecorder.prepare();
	} catch (IOException e) {
		Log.e(TAG, "Couldn't prepare and start MediaRecorder");
	}

	mRecorder.start();
}


// Playback audio using MediaPlayer
private void startPlaying() {

	mPlayer = new MediaPlayer();
	try {
		mPlayer.setDataSource(mFileName);
		mPlayer.prepare();
		mPlayer.start();
	} catch (IOException e) {
		Log.e(TAG, "Couldn't prepare and start MediaPlayer");
	}

}
**參考資源:** - Android中圖片璇轉與縮放 - [http://fecbob.pixnet.net/blog/post/36421426-android%E4%B8%AD%E5%AF%A6%E7%8F%BE%E5%9C%96%E7%89%87%E5%8F%8A%E5%8B%95%E7%95%AB%E7%9A%84%E7%B8%AE%E6%94%BE%E5%92%8C%E6%97%8B%E8%BD%89(%E8%BD%89)](http://fecbob.pixnet.net/blog/post/36421426-android%E4%B8%AD%E5%AF%A6%E7%8F%BE%E5%9C%96%E7%89%87%E5%8F%8A%E5%8B%95%E7%95%AB%E7%9A%84%E7%B8%AE%E6%94%BE%E5%92%8C%E6%97%8B%E8%BD%89(%E8%BD%89)) - About TransitionDrawable - [http://jason1peng.blogspot.tw/2013/01/android-ui-1-cross-fading.html](http://jason1peng.blogspot.tw/2013/01/android-ui-1-cross-fading.html) - TransitionDrawable fade in/out - [http://givemepass.blogspot.tw/2012/03/xml.html](http://givemepass.blogspot.tw/2012/03/xml.html)