2012年4月10日 星期二

[TQC+ Android] 3-10 程式背景音樂 Use MediaPlayer






設計程式背景音樂 , 該音樂可在應用程式執行階段重複播放 , 當手機按下退出鍵或是程式失去焦點時 , 停止播放背景音樂 , 這題的內容在 [TQC+ Android] 3-3 MP3 播放器 Use MediaPlayer 都差不多講完了 , 沒甚麼特別的技巧 , 考試跳出這題的話算你賺到。




package COM.TQC.GDD03;

import android.app.Activity;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.os.Bundle;


public class GDD03 extends Activity
{
  /** Called when the activity is first created. */
  
  MediaPlayer mPlayer;
  
 
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.main);    
     
     try
     {
      mPlayer = MediaPlayer.create(this, R.raw.light);
      mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
      
      mPlayer.setLooping(true);
      //重複播放
      
      //mPlayer.prepare();
      //特別使用註解的方式, 是為了提醒大家, 由於我們先前使用create method建立MediaPlayer
       //create method會自動的call prepare(), 所以我們再call prepare() method會發生 prepareAsync called in state 8的錯誤
         
  }catch (IllegalStateException e)
  {
  // TODO Auto-generated catch block
  e.printStackTrace();
  } 
  }
  
  @Override
  protected void onResume()
  {
 // TODO Auto-generated method stub   
   
   super.onResume();
   mPlayer.start();
  }

  @Override
  protected void onPause()
  {
 // TODO Auto-generated method stub   
   
   super.onPause();
   mPlayer.pause();
  }

  @Override
  protected void onDestroy()
  {
   // TODO Auto-generated method stub   
   super.onDestroy();   
   mPlayer.release();
  }  
}


這題連個Layout都不用設計 , AndroidManifest.xml 也不用特別額外宣告 , 所以都不附上 , 記得先把音樂檔放到 res/raw 資料夾底下就好。




P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....





[TQC+ Android] 3-9 畫布程式 Use Custom SurfaceView





設計一個全螢幕且背景為黑色的畫布程式 , 支援手指單點觸控 , 於手指按下螢幕時捕捉該觸控點的座標 , 以該觸控點座標為基準點繪製一個10*10的白色實心矩形 , 且每一次繪製時皆會清除前一次遺留的矩形 , 這題會使用自訂的SurfaceView , 並且實作處理手勢變化的 OnGestureListener 及實作一個Runnable 讓 non-UI Thread 幫我們處理Canvas的更新。


package COM.TQC.GDD03;

import android.app.Activity;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.SurfaceHolder;


public class GDD03 extends Activity
{
  public static SView mSurfaceView01;
  public static SurfaceHolder mSurfaceHolder01;
  public GestureDetector detector;  
   

  /** Called when the activity is first created. */
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);  
    
    mSurfaceView01 = (SView) this.findViewById(R.id.surfaceView1);
    //從xml檔中取用我們的 CustomView
    
    detector = new GestureDetector(this,mSurfaceView01); 
    //GestureDetector 能幫我們監聽觸控螢幕上的手勢變化
    //它需要一個實作好的 GestureDetector.OnGestureListener interface
    //而這個例子中我們已經傳入 mSurfaceView01 , 因為它實作了 GestureDetector.OnGestureListener
   
  }  
  
  @Override
  public boolean onTouchEvent(MotionEvent event)
  {
 // TODO Auto-generated method stub
   
 //return super.onTouchEvent(event);
 //這是原本method 預設的回傳值 , 在這個例子使用 detector.onTouchEvent(event) 取代
    
 return detector.onTouchEvent(event);
 //GestureDetector 會根據傳入的MotionEvent 判斷手勢的變化 
 //另外它只會判斷Touch events 所以我們將Touch screen傳入的 events 
 //送到 GestureDetector class 的 onTouchEvent(event) , 讓它判斷是哪一種手勢
 //此外GestureDetector 不接受 trackball events  
 
  }
}


在這個class中先宣告Custom SurfaceView : mSurfaceView01 , mSurfaceView01 已經獨立出來存在另一個檔案 SView.java , 另外再使用 GestureDetector 替 mSurfaceView01 註冊監聽事件 , 讓 mSurfaceView01 可以根據手勢變化回應我們想要的動作。


當我們收到 Touch Screen 所傳入的Motion Events , 再將Events 傳入GestureDetector class 中的 onTouchEvent(event) method 去進一步的判斷是哪種手勢 , 再回應相對應的動作。



package COM.TQC.GDD03;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.GestureDetector.OnGestureListener;


public class SView extends SurfaceView implements SurfaceHolder.Callback , OnGestureListener , Runnable 
{ 
 SurfaceHolder mSurfaceHolder01;
 Canvas mCanvas;
 
 //我們已經在xml中定義了自訂的 SurfaceView , 所以系統會選用這個建構子去產生instance
 public SView(Context context, AttributeSet attrs)
 {
  // TODO Auto-generated constructor stub
  
  super(context,attrs);  
  
  mSurfaceHolder01 = this.getHolder();
  //獲取holder , 用來存取及控制 SurfaceView
  
  mSurfaceHolder01.addCallback(this);
  //監聽SurfaceHolder的事件
  
  setFocusable(true);
 } 

 @Override
 public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3)
 {
  // TODO Auto-generated method stub  
 }

 @Override
 public void surfaceCreated(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub 
  
  new Thread(this).start();
  //當Surface 被建立時 , 產生一個Thread來處理畫布內容的改變
 }

 @Override
 public void surfaceDestroyed(SurfaceHolder arg0)
 {
  // TODO Auto-generated method stub
  
  ThreadFlag = false;
  //終止Thread 的運作  
 }
 
 @Override
 public void onDraw(Canvas canvas)
 {   
  // onDraw() method 已經有 SurfaceHolder.lockCanvas() 的效果了
  // 我們也不用額外的對canvas 執行unlock 的動作
  
  canvas.drawColor(Color.BLACK);
  //把畫布填充為黑色    
  Paint p = new Paint();
  p.setColor(Color.WHITE);
       
  if(DrawFlag) canvas.drawRect(x-5, y-5, x+5, y+5, p);
  // 畫一個矩形  
     
  DrawFlag = false;
  //關閉更新畫布開關 , 直到下一次的Touch 事件發生
 }

 float x , y;
 boolean ThreadFlag = true , DrawFlag = false;

 @Override
 public boolean onDown(MotionEvent arg0)
 {
  // TODO Auto-generated method stub
  
  x = arg0.getRawX();
  y = arg0.getRawY();
  //獲取觸控的座標
  
  DrawFlag = true;
  //開啟更新畫布的開關 
  
  return true;
 }
 
 @Override
 public void run()
 {
  // TODO Auto-generated method stub
  
  while(ThreadFlag)
  {
   if(DrawFlag) this.postInvalidate();
   //由於我們是在non-UI Thread中要求要更新畫布
   //所以使用 postInvalidate() method
  }
 }  

 @Override
 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
   float velocityY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onLongPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
   float distanceY)
 {
  // TODO Auto-generated method stub
  return false;
 }
 @Override
 public void onShowPress(MotionEvent e)
 {
  // TODO Auto-generated method stub  
 }
 @Override
 public boolean onSingleTapUp(MotionEvent e)
 {
  // TODO Auto-generated method stub  
  Log.d("Test", "onSingleTapUp");
  return true;
 }
}


至於我們自訂的SurfaceView 它是如何運作的? 我們會在Surface Created 的時候開始run一個non-UI Thread , 這個Thread 專門負責去呼叫 postInvalidate() method 來更新Canvas , 此外postInvalidate() method 會去執行 onDraw() method , 所以我們將畫布要呈現的內容都寫在onDraw() 中等著被執行 , 此外使用一個flag來控制畫布更新的時機 (是在觸控到螢幕時) , 此外當Surface Destroyed 的時候終止Thread 的運作。




接下來是 main.xml的部分 , 請注意我們是使用自訂的SurfaceView
    










P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....







2012年4月8日 星期日

[TQC+ Android] 3-8 判斷地標所屬區域 Use LocationManager




設計判斷地標所屬區域 , 設計一個判讀程式 , 判斷使用者手持手機時 , 該使用者是否在小巨蛋的區域內 , 一旦手機進入或是走出小巨蛋的區域 , 即變更TextView 的提示文字 , 以下是程式部分。



package COM.TQC.GDD03;


import android.app.Activity;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;


public class GDD03 extends Activity implements LocationListener
{
  public String strLocationProvider = "";
  public TextView mTextView01;
  LocationManager LManager;  
  double latitute , longtitute;
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    mTextView01 = (TextView)findViewById(R.id.myTextView1); 
    getLocationPrivider();
  }
  
  public void getLocationPrivider()
  {
    try
    {
      Criteria mCriteria01 = new Criteria();
      mCriteria01.setAccuracy(Criteria.ACCURACY_FINE);
      //定位精準標準 , Criteria.ACCURACY_FINE 為精確      
      mCriteria01.setAltitudeRequired(false);
      //是否需要海拔高度的資訊
      mCriteria01.setBearingRequired(false);
      //是否需要方向資訊
      mCriteria01.setCostAllowed(true);
      //是否同意用到付費的GPS Provider
      mCriteria01.setPowerRequirement(Criteria.POWER_LOW);
      //設定定位所需要的的電力等級
      
      LManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
      strLocationProvider = LManager.getBestProvider(mCriteria01, true);
      //依照我們上面所制定的要求 , 取得最佳的定位方式(定位衛星)
      //由於我是使用模擬器 , 所以回傳的定位提供者是Networks
      //由於我們想要的是一個GPS , 所以之後我們會使用 LocationManager.GPS_PROVIDER
        
    }
    catch(Exception e)
    {
      e.printStackTrace();
    }
  }  

  @Override
  protected void onResume()
  {
 // TODO Auto-generated method stub
 super.onResume();
 
 Location location = LManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
 //透過 getLastKnownLocation() method 可以得知上一次定位提供者所提供的Location資訊
 //所以程式在一開始執行時就可以判斷上一次的座標是否落在小巨蛋內
 
 if(location != null) getWhere(location);
 
 LManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 100, this);
 //註冊監聽事件 , 最小更新區間為5000 milisecond , 100 meter
 
  }
  
  @Override
  protected void onPause()
  {
 // TODO Auto-generated method stub
 super.onPause();
 LManager.removeUpdates(this);
 //移除更新座標的要求
  }
  
  @Override
  public void onLocationChanged(Location location)
  {
 // TODO Auto-generated method stub
 getWhere(location);
 //當偵測到座標改變時會取得座標
  }

  private void getWhere(Location location)
  {
 // TODO Auto-generated method stub
 latitute = location.getLatitude();
 longtitute = location.getLongitude();
 
 Log.d("Test", "getLatitude():"+latitute+" getLongitude()"+longtitute);
 
 //以下是判斷座標是否座落在小巨蛋內的方法
 
 if(latitute > 25.051578 || latitute < 25.051201 || longtitute>121.549666 || longtitute<121.549197)
    {
  //以這四個點作判斷剛好可以圍成一個長方形的區域 , 區域外的部分一定是小巨蛋的區域之外
  //但是這個長方形內仍有一小部分的區域仍不屬於小巨蛋 , 剩下這一小部分區域是一直角三角形
     mTextView01.setText("Outside Taipei Area");
    }
    else
    {
     //接下來我們可以使用向量外積的方式來判斷座標是否落在這直角三角形內 , CrossProducts 超過 0 代表落在直角三角形內也就是不在小巨蛋裡
     
     double ModelLineVectorX = 25.051216 - 25.051201 , ModelLineVectorY = 121.549666 - 121.549197;
     double UserLineVectorX = latitute - 25.051201 , UserLineVectorY = longtitute - 121.549197;
     
     double CrossProducts = UserLineVectorX * ModelLineVectorY - ModelLineVectorX * UserLineVectorY;
     
     if(CrossProducts <= 0) mTextView01.setText("Within Taipei Area");
     else mTextView01.setText("Outside Taipei Area"); 
    }
  }

  @Override
  public void onProviderDisabled(String provider)
  {
 // TODO Auto-generated method stub 
  }

  @Override
  public void onProviderEnabled(String provider)
  {
 // TODO Auto-generated method stub 
  }

  @Override
  public void onStatusChanged(String provider, int status, Bundle extras)
  {
 // TODO Auto-generated method stub 
  }    
}



再來是 AndroidManifest.xml , 因為我們要存取使用者的座標還要使用網路 , 所以要記得去註冊所需要的 use-permission。



    
    
    
    
    
        
            
                
                
            
        

    
    

 



使用模擬器來發送模擬的座標







P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....




[Android] 使用 custom action strings 判斷簡訊發送狀態 Use SmsManager, PendingIntent





BroadcastReceiver的使用有兩種方法 , 之前文章所使用的技巧是動態的註冊和註銷 : [TQC+ Android] 3-7 判斷髮送簡訊狀態 Use registerReceiver, SmsManager, PendingIntent , 而這篇要使用的是在 AndroidManifest.xml 中靜態註冊 , 並在<intent-filter> tag 宣告使用我們自訂的action strings , 以下是程式碼。


package COM.TQC.GDD03;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class GDD03 extends Activity
{
  private Button mButton1;
  private EditText mEditText1, mEditText2;
  
  PendingIntent sentIntent;
  SmsManager smsManager;  
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    /* Phone No. */
    mEditText1 = (EditText) findViewById(R.id.myEditText1);
    
    /* SMS BODY */
    mEditText2 = (EditText) findViewById(R.id.myEditText2);
    mButton1 = (Button) findViewById(R.id.myButton1);
    
    mEditText1.setText("5556");
    mEditText2.setText("DAVID_SEND");
    
    smsManager = SmsManager.getDefault();
    //SmsManager 類別可以協助我們送出簡訊 , 我們可以透過 這個static method getDefault()來取得 SmsManager預設的instance
    
    Intent smsIntent = new Intent("COM.TQC.GDD03.Send_Message");
 //替這個Intent 設定我們自訂的action : COM.TQC.GDD03.Send_Message
    //通常在action前我們會附加上 application package name
    
    smsIntent.putExtra("Data", "Hello!");
    //順便附加個字串
    
 sentIntent = PendingIntent.getBroadcast(GDD03.this, 0, smsIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 //public static PendingIntent getBroadcast (Context context, int requestCode, Intent intent, int flags)
 //使用這個static method 我們會接收到一個具有像Context.sendBroadcast()行為的PendingIntent instance.
 //我們使用 FLAG_CANCEL_CURRENT , 用意是在產生一個新的 PendingIntent之前 , 會把舊的先cancel掉
 
    
    mButton1.setOnClickListener(new Button.OnClickListener()
    {
      @Override
      public void onClick(View v)
      {
        // TODO Auto-generated method stub
     smsManager.sendTextMessage(mEditText1.getText().toString(), null, mEditText2.getText().toString(), sentIntent, null);
     //送出簡訊 , 並附加一個 PendingIntent sentIntent , 用意是等它回報傳送結果(result code)
     //並且可以從sentIntent中取得相關資訊 , 例如一些額外的錯誤提示 , 或是我們預先加入的資訊如之前的"Data" 
      
      }
    });
  } 
  
  public static class mServiceReceiver extends BroadcastReceiver
  {
   @Override
      public void onReceive(Context context, Intent intent)
      {
        // TODO Auto-generated method stub
       
       Log.d("Test","Data:"+intent.getStringExtra("Data"));
       //顯示之前擺入sentIntent的附加資訊
       
       if(intent.getAction().equals("COM.TQC.GDD03.Send_Message"))
       { 
        switch(getResultCode()) //取得回傳的result code
        {
         case Activity.RESULT_OK:       
         Toast.makeText(context, "SMS sent", Toast.LENGTH_LONG).show();
         break;
        
         case SmsManager.RESULT_ERROR_RADIO_OFF:
         Toast.makeText(context, "RADIO OFF", Toast.LENGTH_LONG).show();
            break;
         
         case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
         //如果是這個"一般錯誤"的case , 那麼 sentIntent 也許會附帶一組 "errorCode" 來記錄錯誤資訊
         break;
         
         case SmsManager.RESULT_ERROR_NULL_PDU:
            //Failed because no pdu provided 
            break;
            
         case SmsManager.RESULT_ERROR_NO_SERVICE:
            //Failed because service is currently unavailable
            break;
        
         default:       
         Toast.makeText(context, "NO SERVICE", Toast.LENGTH_LONG).show();
            break;
        }      
       }
      }
  }
}


程式的處理流程在註解中已經有提到了 , 不清楚的可以搭配之前的文章。

我們將 custom BroadcastReceiver 定義為 public static 一個內部靜態類別 , 又或者可以另外 custom BroadcastReceiver 單獨分開擺在另一個 Java 檔中 , 以上兩個不同的方式在AndroidManifest.xml 中也搭配不一樣的宣告內容。

    
        
            
                
                
            
        
        
            
                
            
               
    
    

 
注意 <receiver android:name="GDD03$mServiceReceiver"> , 因為我們將 mServiceReceiver 定義為內部靜態類別 , 而  GDD03$mServiceReceiver  是一個標準的方法 , 可以協助我們取用內部類別。


2012年4月7日 星期六

[TQC+ Android] 3-7 判斷發送簡訊狀態 Use registerReceiver, SmsManager, PendingIntent





設計一發送簡訊的程式 , 並可得知發送之後的送達結果 , 這題使用BroadcastReceiver 等待程式發送簡訊的動作 , 而關於BroadcastReceiver的使用有兩種方法 , 一種是靜態的在AndroidManifest.xml中在<receiver> tag中註冊 , 另一種是動態的在程式中使用 registerReceiver() method 註冊和使用unregisterReceiver()註銷 , 另外既然要讓 BroadcastReceiver 可以判斷發送簡訊的動作 , 就要在 IntentFilter 中增加發送簡訊的action , 但是 Android SDK 沒有提供相關的action , 所以我們必須使用我們自己的 custom action string , 而這題我們使用動態註冊及註銷的方式搭配自訂的 action 來達成題目要求。




package COM.TQC.GDD03;

import android.app.Activity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class GDD03 extends Activity
{
  private Button mButton1;
  private EditText mEditText1, mEditText2;
  
  PendingIntent sentIntent;
  SmsManager smsManager;
  mServiceReceiver mServiceReceiver;
    
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    /* Phone No. */
    mEditText1 = (EditText) findViewById(R.id.myEditText1);
    
    /* SMS BODY */
    mEditText2 = (EditText) findViewById(R.id.myEditText2);
    mButton1 = (Button) findViewById(R.id.myButton1);
    
    mEditText1.setText("5556");
    mEditText2.setText("DAVID_SEND");
    
    mServiceReceiver = new mServiceReceiver();
    //宣告一個自訂的BroadcastReceiver , 稍後我們會在onResume() 動態註冊它
    
    smsManager = SmsManager.getDefault();
    //SmsManager 類別可以協助我們送出簡訊 , 我們可以透過 這個static method getDefault()來取得 SmsManager預設的instance
    
    Intent smsIntent = new Intent("COM.TQC.GDD03.Send_Message");
 //替這個Intent 設定我們自訂的action : COM.TQC.GDD03.Send_Message
    //通常在action前我們會附加上 application package name
    
    smsIntent.putExtra("Data", "Hello!");
    //順便附加個字串
    
 sentIntent = PendingIntent.getBroadcast(GDD03.this, 0, smsIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 //public static PendingIntent getBroadcast (Context context, int requestCode, Intent intent, int flags)
 //使用這個static method 我們會接收到一個具有像Context.sendBroadcast()行為的PendingIntent instance.
 //我們使用 FLAG_CANCEL_CURRENT , 用意是在產生一個新的 PendingIntent之前 , 會把舊的先cancel掉
 
    mButton1.setOnClickListener(new Button.OnClickListener()
    {
      @Override
      public void onClick(View v)
      {
        // TODO Auto-generated method stub       
       
       smsManager.sendTextMessage(mEditText1.getText().toString(), null, mEditText2.getText().toString(), sentIntent, null);
       //送出簡訊 , 並附加一個 PendingIntent sentIntent , 用意是等它回報傳送結果(result code)
       //並且可以從sentIntent中取得相關資訊 , 例如一些額外的錯誤提示 , 或是我們預先加入的資訊如之前的"Data" 
      }
    });
  }
  
  public class mServiceReceiver extends BroadcastReceiver
  {
    @Override
    public void onReceive(Context context, Intent intent)
    {
      // TODO Auto-generated method stub
     
     Log.d("Test","Data:"+intent.getStringExtra("Data"));
     //顯示之前擺入sentIntent的附加資訊
     
     if(intent.getAction().equals("COM.TQC.GDD03.Send_Message"))
     { 
      switch(getResultCode()) //取得回傳的result code
      {
       case Activity.RESULT_OK:
       showToast("SMS sent");
       break;
      
       case SmsManager.RESULT_ERROR_RADIO_OFF:
       showToast("RADIO OFF");
       break;
       
       case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
       //如果是這個"一般錯誤"的case , 那麼 sentIntent 也許會附帶一組 "errorCode" 來記錄錯誤資訊
       break;
       
       case SmsManager.RESULT_ERROR_NULL_PDU:
          //Failed because no pdu provided 
          break;
          
       case SmsManager.RESULT_ERROR_NO_SERVICE:
          //Failed because service is currently unavailable
          break;
      
       default:
       showToast("NO SERVICE");
       break;
      }      
     }
    }
  }
   
  @Override
  protected void onResume()
  {
    // TODO Auto-generated method stub
    super.onResume();
    IntentFilter IFilter = new IntentFilter();
    IFilter.addAction("COM.TQC.GDD03.Send_Message");
    //宣告一個IntentFilter並使用我們之前自訂的action
    
    registerReceiver(mServiceReceiver,IFilter);
    //動態註冊BroadcastReceiver
  }
  
  @Override
  protected void onPause()
  {
    // TODO Auto-generated method stub
    super.onPause();
    unregisterReceiver(mServiceReceiver);
    //動態註銷BroadcastReceiver
  }
  
  public void showToast(String message)
  {
   Toast.makeText(this, message, Toast.LENGTH_LONG).show();
  }
}


在這裡有幾個重點要說明一下 , 我們選擇在 onResume() method去動態註冊custom BroadcastReceiver : mServiceReceiver , 然後選擇在onPause()的時候去註銷它 , 那是因為當程式進入pause狀態時 , 是沒辦法接收到 intent的。


另外根據PendingIntent的操作行為 , 我們選用不同的method 取得它的instance , 而它有三個不同的 static method可以協助我們取得instance , 分別是 getActivity(Context, int, Intent, int)getBroadcast(Context, int, Intent, int),getService(Context, int, Intent, int) 在這題我們要讓mServiceReceiver 可以接收到傳送簡訊的action , 所以使用getBroadcast() , 而 PendingIntent 就會附加在 SmsManager.sendTextMessage() 當傳送簡訊時被sent出去


SmsManager 類別中的 
public void sendTextMessage (String destinationAddress, String scAddress, String text, PendingIntent sentIntent,PendingIntent deliveryIntent) 可以協助我們送出簡訊 , 特別是如果參數 PendingIntent sentIntent 不是null的話 , 當傳送簡訊後可以透過 getResultCode() 取得回報的 result code , 我們再根據這個 result code去判斷簡訊發送的情形是否成功 , 或是有哪些錯誤訊息。




接下來是AndroiManifest.xml , 請記得由於我們有發送簡訊這個動作 , 所以記得宣告 use-permission : android.permission.SEND_SMS

    
        
            
                
                
            
        
    
    

 




最後是main.xml 

  
  
  
  
  
  
  
  
  
  
  














由於題目要求能判斷無線網路關閉的狀態 , 所以去設定無線裝置及網路的地方啟動飛航模式。



成功的判斷出因為沒有網路所以沒辦法成功發送簡訊的狀態




開啟另一個5556模擬器 , 可以看到畫面上有出現接收到簡訊的提示。



P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....



2012年4月5日 星期四

[TQC+ Android] 3-6 查詢地圖 Use MapView , Google Map API 2.0





設計查詢地圖 , 可輸入想查詢的經緯度 , 於輸入後點選查詢 , 可顯示欲查詢地圖並可放大縮小地圖 , 這題的重點是在申請 Android Maps API Key , 過程是先從開發環境得電腦中取得Keystore 的MD5編碼 , 再透過Google網站簽暑便可取得了 , 另外因為必須使用到Google API 所以在建立Android 專案時 , Build target 記得選擇有Google API。


以下是取得Android Maps API Key的步驟:


1. 至開發環境 Eclipse IDE 中
    Window > Preferences > Android > Build > Default debug keystore 取得 keystore的存放路徑








2. 進入Dos模式 . 將目錄切換至JAVA資料夾下的\jre\bin, 範例路徑: C:\Java\jdk1.6.0\17\jre\bin
   (視電腦安裝路徑而定) , 然後在目錄下輸入以下指令 , 取得MD5編碼。


keytool -list -alias androiddebugkey -keystore "第一步驟的keystore的存放路徑" -storepass android -keypass android






3. 指令執行成功之後 , 會產生 keystore 檔案 , 並可取得一組MD5編碼 , 然後至以下網址輸入MD5編碼 , 取得Android Maps API Key。


https://code.google.com/intl/zh-TW/android/maps-api-signup.html




圖片中最下面那段xml配置範例是之後要加入到MapView的android:apiKey屬性值中的。


接下來我們來看看程式碼的部分。


package COM.TQC.GDD03;


import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle; 
import android.view.View; 
import android.widget.Button; 
import android.widget.EditText; 
import com.google.android.maps.GeoPoint; 
import com.google.android.maps.MapActivity; 
import com.google.android.maps.MapController; 
import com.google.android.maps.MapView; 


public class GDD03 extends MapActivity 
{
  private MapController MapController; 
  private MapView MapView;
  private Button btn_query, btn_zoomIn, btn_zoomOut; 
  private EditText EditLongitude; //經度
  private EditText EditLatitude; //緯度
  private int intZoomLevel=0;
  
  private double dLng=121.228626;//預設經度
  private double dLat=25.0402226; //預設緯度
  

  @Override 
  protected void onCreate(Bundle icicle) 
  { 
    super.onCreate(icicle); 
    setContentView(R.layout.main); 

    EditLongitude = (EditText) this.findViewById(R.id.editText1);
    EditLongitude.setText(dLng+"");
    EditLatitude = (EditText) this.findViewById(R.id.editText2);
    EditLatitude.setText(dLat+"");
    
    intZoomLevel = 15;
    
    MapView = (MapView) findViewById(R.id.mapview);    
    
    //MapView.setBuiltInZoomControls(false);
    //參數為True時 , 使用者可以直接控制MapView的縮放大小 
    //在這邊使用false讓使用者使用我們的按鈕來控制MapView的縮放大小
    
    MapView.setStreetView(true);
    //顯示街道圖
    MapController = MapView.getController();
 refreshMapView();    
    
 btn_query = (Button)findViewById(R.id.btn_query); 
    btn_query.setOnClickListener(new Button.OnClickListener() 
    { 
      //@Override
      public void onClick(View v) 
      {        
        if(EditLongitude.getText().toString().equals("")||
           EditLatitude.getText().toString().equals(""))
        {
          showDialog("經度或緯度填寫不正確!");
        }
        else
        {        
         showDialog("顯示地圖");
         refreshMapView(); 
        }
      } 
    });      
    
    btn_zoomIn = (Button)findViewById(R.id.btn_zoomIn); 
    btn_zoomIn.setOnClickListener(new Button.OnClickListener() 
    { 
      //@Override 
      public void onClick(View v) 
      { 
       intZoomLevel++;
       MapController.setZoom(intZoomLevel);    
      } 
    });      
    
    btn_zoomOut = (Button)findViewById(R.id.btn_zoomOut); 
    btn_zoomOut.setOnClickListener(new Button.OnClickListener() 
    { 
      //@Override
      public void onClick(View v) 
      { 
       intZoomLevel--;    
       MapController.setZoom(intZoomLevel);
      } 
    });
  } 
  
  GeoPoint GPoint;  
  
  public void refreshMapView() 
  {      
     dLng = Double.parseDouble(EditLongitude.getText().toString());
     dLat = Double.parseDouble(EditLatitude.getText().toString());
     GPoint = new GeoPoint((int)(dLat*1E6),(int)(dLng*1E6));
     //GeoPoint GPoint = new GeoPoint(19240000,-99120000);
     MapController.setCenter(GPoint);
     //將MapView的中心點定位在GPoint的位置
     MapController.setZoom(intZoomLevel);
    //設定地圖縮放比例
     MapView.invalidate(); 
  } 
   
  @Override 
  protected boolean isRouteDisplayed() 
  { 
    return false;
    //為了計算目的的需要 , 要讓Server知道我們是否正在顯示相關的路由資訊
    //在這個應用中是用不到 , 所以我們回傳false
  }
    
  private void showDialog(String mess)
  {
    new AlertDialog.Builder(GDD03.this).setTitle("Message")
    .setMessage(mess)
    .setNegativeButton("確定", new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface dialog, int which)
      {}
    })
    .show();
  }
} 




由於MapActivity class 不在Android 官方內建的API中 , 我們必須在AndroidManifest.xml中引入 library : com.google.android.maps , 另外我們會存取使用者的當前座標所以還必須註冊相關的 use-permission 。

    
    
    
       
        
        
            
                
                
            
        
          
    
    
 




再來看看layout檔案

    






先前提到MapActivity class 不是Android 官方的API , 所以在官方的開發函式文件中是找不到相關資料的 , 下面這個網站可取得相關的資料。


Google Map APIs




P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....







2012年4月1日 星期日

[TQC+ Android] 3-5 接聽來電 Use PhoneStateListener , ContentResolver





設計接聽來電程式 , 以程式接收來自DDMS的模擬來電 , 並顯示來電號碼於畫面 , 開啟程式之後就等待來電 , 不過倒不是使用BroadcastReceiver , 而是使用 PhoneStateListener 來監聽來電狀態 , 接到來電之後再以電話號碼搜尋通訊錄 , 找出來電的人是誰 , 以下是程式碼。


package COM.TQC.GDD03;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.widget.TextView;

public class GDD03 extends Activity
{
 
  public TextView myTextView1;

  /** Called when the activity is first created. */
  
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    myTextView1 = (TextView) this.findViewById(R.id.textView1);
    
    exPhoneCallListener myPhoneStateListener = new exPhoneCallListener();
        
    TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    //取得TelephonyManager    
    
    telephonyManager.listen(myPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
    //將電話狀態的Listener加到取得的TelephonyManager中
    //在這裡我們選擇要聆聽的狀態為PhoneStateListener.LISTEN_CALL_STATE
  }  
   
  //運用PhoneStateListener監聽電話狀態  
  class exPhoneCallListener extends PhoneStateListener
  {    
    //PhoneStateListener 中不同的聆聽事件所要override的method各有不同, 請針對之前註冊的聆聽事件相對應的method進行override
 //我們之前選擇的聆聽事件為 LISTEN_CALL_STATE , 所以override onCallStateChanged() method 
 public void onCallStateChanged(int state, String incomingNumber)
    {
     switch (state)
     {
        //電話狀態是閒置的
        case TelephonyManager.CALL_STATE_IDLE:                    
           break;
        
           //電話狀態是接起的       
        case TelephonyManager.CALL_STATE_OFFHOOK:                
           break;
           
           //電話狀態是響起的        
        case TelephonyManager.CALL_STATE_RINGING:           
        getContactPeople(incomingNumber);
        //由電話號碼查詢聯絡人
        break;
        
        default:
        break;
     }
     
      super.onCallStateChanged(state, incomingNumber);
    }
  }

  private void getContactPeople(String incomingNumber)
  {
    myTextView1.setTextColor(Color.BLUE);
    
    ContentResolver contentResolver = getContentResolver();
    //必須靠ContentResolver的協助取得電話簿的資料
    
    Cursor cursor = null;
    
    String[] projection = new String[]
    {PhoneLookup._ID, PhoneLookup.DISPLAY_NAME, PhoneLookup.LOOKUP_KEY};
    //在query後我們要求ContentResolver回傳以上欄位的資料內容
    
    Uri uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(incomingNumber));
    //將來電號碼append上去當作篩選條件 , 效果等於WHERE = incomingNumber
    
    cursor = contentResolver.query(uri, projection, PhoneLookup.NUMBER + " = ?", new String[] {incomingNumber}, null);
    /*
     * 因為我們所要查詢手機中通訊錄的資料, 所以使用ContentResolver class 來存取Content model
     * 我們可以將 PhoneLookup.CONTENT_FILTER_URI 這個參數視為想要存取的Table, 你也可以使用 null 他就會回傳所有的column 不過這樣比較沒有效率
     * PhoneLookup.NUMBER + "?" 這個字串相當於SQL語法中的 WHERE , 搭配後面的參數 new String[] {incomingNumber}
     * 其實可以寫成這樣 PhoneLookup.NUMBER = incomingNumber
     * 
     * public final Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
     * 
     * uri : The URI, using the content:// scheme, for the content to retrieve.
     * projection : A list of which columns to return. Passing null will return all columns, which is inefficient.
     * selection A filter declaring which rows to return, formatted as an SQL WHERE clause (excluding the WHERE itself). Passing null will return all rows for the given URI.
     * selectionArgs You may include ?s in selection, which will be replaced by the values from selectionArgs, in the order that they appear in the selection. The values will be bound as Strings.
     * sortOrder How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.
     * 
     */  
    
    if (cursor.getCount() == 0) //判斷此電話號碼存不存在電話簿中, 不存在的話使用 unknown Number
    {
      myTextView1.setText("unknown Number:" + incomingNumber);
    } 
    else if (cursor.getCount() > 0) //這來電號碼存在電話簿中 , 顯示聯絡人姓名
    {
      cursor.moveToFirst();      
      
      String name = cursor.getString(1);
      //取得index 1 欄位的字串型態資料, 關於欄位的順序就是當初我們 projection 字串陣列中的字串順序
      //如果當初的參數為 null,那它會回傳所有的column, 這樣最好搭配 getColumnIndex(String ColumnName)來找尋指定column的資料 , 而回傳的資料型態同樣必須查詢文件才能得知
      
      myTextView1.setText(name + ":" + incomingNumber);
    }
  }
}


這個程式的運作流程開頭有大概說明了 , 程式中註解也進一步解釋了 , 在這邊在稍微補充 , PhoneStateListener 可以監控手機裝置的狀態 , 訊號強度 來電狀態 基地台地點....等等不少事件, , PhoneStateListener 中不同的監控事件所要override的method各有不同, 請針對之前註冊的監控事件相對應的method進行override , 如果想查詢監控事件所對應到的method可以造訪這個文件 : PhoneStateListener 




接下來是AndroidManifest.xml , 別忘了因為我們要監控來電的狀態所以要註冊 <uses-permission android:name="android.permission.READ_PHONE_STATE"/> 另外讀取通訊錄也需要註冊權限 : <uses-permission android:name="android.permission.READ_CONTACTS"/>
    
    
    
        
            
                
                
            
        
    
    
 















P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....







[TQC+ Android] 3-4 接收簡訊 Use SmsMessage , BroadcastReceiver





設計接收簡訊程式 , 以DDMS模擬產生一則簡訊 , 簡訊送達時使用Toast 顯示傳送者電話及簡訊內容 , 這題實作的技巧其實在之前的文章 [TQC+ Android] 3-2 接收SMS Use BroadcastReceiver, SmsMessage, onNewIntent 都講完了 , 而且這題只是單純的顯示接收到的簡訊內容 , 記得註冊user-permission 和 "android.provider.Telephony.SMS_RECEIVED" , 相當的簡單 , 以下是程式碼。


package COM.TQC.GDD03;

import android.app.Activity; 
import android.os.Bundle; 
import android.widget.TextView; 

public class GDD03 extends Activity 
{ 
  private TextView mTextView1; 
  /** Called when the activity is first created. */ 
  @Override 
  public void onCreate(Bundle savedInstanceState) 
  { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.main); 
     
    mTextView1 = (TextView) findViewById(R.id.myTextView1); 
    mTextView1.setText("等待接收簡訊..."); 
  }
}
這是一個等待簡訊進來的 main Activity。






接下來是 GDD03_SM_Receiver.java , 是用來等待簡訊事件的


package COM.TQC.GDD03 ;

import java.util.Set;

import android.content.BroadcastReceiver; 
import android.content.Context; 
import android.content.Intent; 
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;



public class GDD03_SM_Receiver extends BroadcastReceiver 
{ 
 
  @Override 
  public void onReceive(Context context, Intent intent) 
  { 
    // TODO Auto-generated method stub 
    
    Toast t = Toast.makeText(context, "", Toast.LENGTH_LONG);
   
 if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED"))
    {     
  Bundle bundle = intent.getExtras();
  
  //檢查一下返回的Bundle裡面有哪些的資訊, 檢查Key
  Set KeySet = bundle.keySet();
     
     for(String s : KeySet)
     {
        Log.d("We have key elements", "Key:"+s);
     }
          
  Object messages[] = (Object[]) bundle.get("pdus");
  SmsMessage[] smsMessage = new SmsMessage[messages.length];
  for (int n = 0; n < messages.length; n++)
  {
   smsMessage[n] = SmsMessage.createFromPdu((byte[]) messages[n]);
      //將原始的PDU格式的資料轉為smsMessage的格式, 由於傳入的簡訊由於長度的限制可能不止一封
  }
  
     t.setText("接收到來自:"+smsMessage[0].getOriginatingAddress()+"\n----傳來的簡訊----\n"+smsMessage[0].getDisplayMessageBody());
     t.show();
    }
    else
    {
     t.setText(intent.getAction());
     t.show();
    }   
  } 
} 






最後別忘了 AndroidManifest.xml

    
    
        
            
                
                
            
        
        
            
                
            
        
    
    
 






P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....



[TQC+ Android] 3-3 MP3 播放器 Use MediaPlayer






設計MP3播放器 , 畫面上顯示三個按鍵 , 由左至右排成一列 , 分別是播放 暫停 停止 , 按鍵上方顯示程式狀態 , 這題除了基本的初始化MediaPlayer資源之外就是流程控制了, 另外程式也註冊了當MediaPlayer播放完畢和發生錯誤的Listener事件 , 一開始附上了一張MediaPlayer從初始化到釋放資源的流程控制狀態圖 ,  另外請先載入檔案至res/raw 資料夾底下, 以下是程式碼。




package COM.TQC.GDD03;


import android.app.Activity; 
import android.media.AudioManager;
import android.media.MediaPlayer; 
import android.os.Bundle; 
import android.view.View; 
import android.widget.ImageButton;
import android.widget.TextView; 


public class GDD03 extends Activity
{   
   private ImageButton mButtonPlay, mButtonPause, mButtonStop;
   private TextView mTextView; 
   private MediaPlayer mMediaPlayer;
   private boolean bIsPaused = false; 
   
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        //透過findViewById建構子建立TextView與ImageView物件 
       
        mTextView = (TextView) this.findViewById(R.id.textView1);
        mButtonPlay = (ImageButton) this.findViewById(R.id.imageButton1);
        mButtonPause = (ImageButton) this.findViewById(R.id.imageButton2);
        mButtonStop = (ImageButton) this.findViewById(R.id.imageButton3);
        
        mButtonStop.setEnabled(false);  
        mButtonPause.setEnabled(false);
        //一開始將Stop和Pause按鈕都diable, 只允許Play按鈕enable
                 

        // 執行播放音樂的按鈕  
        mButtonPlay.setOnClickListener(new ImageButton.OnClickListener() 
        { 
          //覆寫OnClick事件
          public void onClick(View v) 
          { 
            // TODO Auto-generated method stub         
            try 
            { 
             //判斷是否由暫停狀態開始播放
             if(!bIsPaused)
             {              
              ini();
              //將音樂以Import的方式儲存在res/raw/always.mp3
              //目前狀態為Stop狀態, 由於Stop狀態會release Media, 所以要再額外重新ini() Media 一次 
             }
             
            } 
            catch (Exception e) 
            { 
              // TODO Auto-generated catch block              
              e.printStackTrace(); 
            } 
            finally
            { 
             mMediaPlayer.start();
             //撥放音樂
             
             mTextView.setText(getText(R.string.str_start));
             mButtonPlay.setEnabled(false);
                mButtonPause.setEnabled(true);
                mButtonStop.setEnabled(true);
                //當音樂播放時, enable Pause和Stop按鈕, disable Play按鈕
            }
          } 
        }); 
         
        // 停止播放 
        mButtonStop.setOnClickListener(new ImageButton.OnClickListener() 
        { 
         // @Override
          public void onClick(View arg0) 
          { 
            // TODO Auto-generated method stub 
            try 
            { 
              if (mMediaPlayer != null) 
              { 
               bIsPaused = false;
               mMediaPlayer.stop();
               mMediaPlayer.release();               
               //將Media 資源 release
               
               mTextView.setText(getText(R.string.str_close));
                 mButtonPlay.setEnabled(true);
                  mButtonPause.setEnabled(false);
                  mButtonStop.setEnabled(false);
                  //當停止播放時, disable Pause和Stop按鈕, enable Play按鈕
                  
              }                 
            } 
            catch (Exception e) 
            { 
              // TODO Auto-generated catch block              
              e.printStackTrace(); 
            } 
          } 
        }); 
        
        // 暫停播放  
        mButtonPause.setOnClickListener(new ImageButton.OnClickListener() 
        { 
          //@Override
          public void onClick(View arg0) 
          { 
            // TODO Auto-generated method stub 
            pauseMedia();
          }
        });      
        
         
      } 
    
      //初始建立Media 資源
      public void ini()
      {
       mMediaPlayer = MediaPlayer.create(GDD03.this, R.raw.always);
                 
       mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
       
       //mMediaPlayer.prepare();          
         //特別使用註解的方式, 是為了提醒大家, 由於我們先前使用create method建立MediaPlayer
         //create method會自動的call prepare(), 所以我們再call prepare() method會發生 prepareAsync called in state 8的錯誤
                  
  
  //當MediaPlayer.OnCompletionLister會執行的Listener            
        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() 
        { 
          @Override 
          
          //覆寫檔案播出完畢事件
          public void onCompletion(MediaPlayer arg0) 
          { 
            try 
            {  
             bIsPaused = true;
             mTextView.setText("音樂播完了");
             mButtonPlay.setEnabled(true);
                mButtonPause.setEnabled(false);
                mButtonStop.setEnabled(false);
                //當音樂播放完畢時, disable Pause和Stop按鈕, enable Play按鈕
            
            } 
            catch (Exception e) 
            { 
              mTextView.setText(e.toString()); 
              e.printStackTrace(); 
            } 
          } 
        }); 
         
        //當MediaPlayer.OnErrorListener會執行的Listener 
        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() 
        { 
          @Override //p530
          //覆寫錯誤處理事件
          public boolean onError(MediaPlayer arg0, int arg1, int arg2) 
          { 
            // TODO Auto-generated method stub 
            try 
            {
               
                // arg1為error code分別為,
                // 分別為MEDIA_ERROR_UNKNOWN(1): Unspecified media player error.
                // MEDIA_ERROR_SERVER_DIED(100): Media server died. In this case, the application must release the MediaPlayer object and instantiate a new one.
             // arg2為extra code , an extra code, specific to the error. Typically implementation dependant.
                
             mMediaPlayer.release();
             bIsPaused = false;
            } 
            catch (Exception e) 
            { 
              mTextView.setText("Error Code:"+arg1+" Extra Code:"+arg2+" Error Msg:"+e.toString()); 
              e.printStackTrace(); 
            } 
            return false; 
          } 
        });  
      }
      
      public void pauseMedia()
      {  
       try 
          {          
        bIsPaused = true;
        mMediaPlayer.pause();
             //暫停播放
             mTextView.setText(getText(R.string.str_pause));
             mButtonPlay.setEnabled(true);
              mButtonPause.setEnabled(false);
              mButtonStop.setEnabled(true);
              //當暫停播放時, disable Pause按鈕, enable Play和Stop按鈕                
          } 
          catch (Exception e) 
          { 
            mTextView.setText(e.toString()); 
            e.printStackTrace(); 
          }       
      }

      @Override 
      //覆寫主程式暫停狀態事件
      protected void onPause() 
      { 
        // TODO Auto-generated method stub
        super.onPause();
        pauseMedia();
        //當程式突然因為某些因素被迫中斷例如來電 , 我們會需要將音樂自動暫停
        
      }       
    } 


在這邊要特別無論是在官方的開發文件或是許多教材都提到 , 在start() MediaPlayer之前必須call prepare() method , 讓MediaPlayer物件進入 Prepared state , 但是請注意 ! 如果是使用static  MediaPlayer.create() 這個method 建立MediaPlayer資源的話, 這個method會自動的去call prepare() method , 如果這時候還是在start()之前去call prepare()的話會發生 prepareAsync called in state 8的錯誤。




再來是Layout檔的部分

    
    
    
    



在main.xml文件中 , 已經把三個ImageButton的圖片來源都設定好了。












P.S. 題目中所要求的Variable和Method皆會保留 , 也會根據題目所要求的流程去實作 , 縱使題目要求繞遠路....





Google Analytics