2010年12月15日 星期三

[Android] 如何使用 TextToSpeech (TTS)



這篇是要替上一篇 [Android] 如何使用Google Translate API 和 Spinner 製作翻譯機 所作的翻譯機加上發音功能 , 所以要使用到Android 的 android.speech.tts.TextToSpeech , 對於這個功能Android 官方的部落格有一篇文章介紹得很清楚: An introduction to Text-To-Speech in Android , 大家可以參考一下 , 有些Android 的手機由於儲存的容量限制 , 所以不一定有安裝 TextToSpeech Engine , 所以首先要先檢查TextToSpeech.Engine 是否有被安裝 , 如果沒安裝的話再使用Android API去安裝 , 另外不是每個語言都被支援發音 , 所以我們還要去check一下 , 系統是否支援 , Layout檔案和AndroidManifest.xml都和上一篇一樣沒更改 , 所以這邊只貼程式碼的部分。





package com.android;

import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Button;
import android.widget.Toast;

import com.google.api.translate.Language;
import com.google.api.translate.Translate;

public class GoogleTranslateSpinner extends Activity implements OnItemSelectedListener , Button.OnClickListener , OnInitListener
{
 /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        AAdapter = new ArrayAdapter(this,android.R.layout.simple_spinner_item,LanguageNameArray);
        AAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        //ArrayAdapter設定下拉式清單內容的layout格式
        //android. 開頭的範本是由Android提供的
        
        SpinnerFrom = (Spinner) findViewById(R.id.SpinnerFrom);
        SpinnerFrom.setAdapter(AAdapter);
        SpinnerFrom.setOnItemSelectedListener(this);
        SpinnerTo = (Spinner) findViewById(R.id.SpinnerTo);        
        SpinnerTo.setAdapter(AAdapter);
        SpinnerTo.setOnItemSelectedListener(this);        
        
        EditTextFrom = (EditText) findViewById(R.id.EditTextFrom);
        EditTextTo = (EditText) findViewById(R.id.EditTextTo);
        
        Button = (Button) findViewById(R.id.Button);
        Button.setOnClickListener(this);
        
        Translate.setHttpReferrer("http://shung007.blogspot.com/");
        //Translate.setHttpReferrer(" Enter the URL of your site here");
        //必要!!! 否則程式會出現Exception
        
        Intent checkIntent = new Intent();  
        checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);  
        startActivityForResult(checkIntent, MY_TTS_CHECK_CODE);  
        //前往檢查 TTS Engine有無安裝
    }
    
    final int MY_TTS_CHECK_CODE = 9527;
    TextToSpeech TToSpeech;
    
    Spinner SpinnerFrom , SpinnerTo;
    EditText EditTextFrom , EditTextTo;
    Button Button;
    
    int positionFrom , positionTo;
    
    ArrayAdapter AAdapter;
    
    public String[] LanguageNameArray = new String[]{
            "繁體中文", "CHINESE_SIMPLIFIED", "GERMAN", "ENGLISH",
            "JAPANESE", "FRENCH"
        };
    //作為顯示在Spinner裡的清單內容
    
    public Language[] LanguageCode = new Language[]{
        Language.CHINESE_TRADITIONAL, Language.CHINESE_SIMPLIFIED,
        Language.GERMAN, Language.ENGLISH, Language.JAPANESE, Language.FRENCH      
    };
    //相對應的LanguageCode , 作為之後傳遞的引數用    
    
    public Locale[] LocaleCode = new Locale[]{
      Locale.CHINESE, Locale.CHINA,
      Locale.GERMAN, Locale.ENGLISH, Locale.JAPANESE, Locale.FRENCH      
     };
    //相對應的Locale值 , 準備替我們所選擇的語言發音

    @Override  
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {  
      // TODO Auto-generated method stub  
      if(requestCode == MY_TTS_CHECK_CODE)
      {      
       if(resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) //如果TTS Engine有成功找到的話
          {         
          TToSpeech = new TextToSpeech(this, this);
            //宣告一個 TextToSpeech instance
         //並註冊android.speech.tts.TextToSpeech.OnInitListener
            //當TTS Engine 初始完畢之後會呼叫 onInit(int status)
          Log.d("onActivityResult" , "onInit");
          }
          else //如果TTS Engine 沒有安裝的話 , 要求API安裝  
          {            
             Intent installIntent = new Intent();  
             installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);  
             startActivity(installIntent);  
          }  
      }
    }
    
 @Override
 public void onItemSelected(AdapterView parent, View view, int position,
   long arg)
 {  
  if(parent.equals(SpinnerFrom)) positionFrom = position;
  else positionTo = position;
  //判斷並記錄使用者選擇了哪個語言翻譯
 }

 @Override
 public void onNothingSelected(AdapterView arg0)
 {
  // TODO Auto-generated method stub  
 }

 @Override
 public void onClick(View ButtonView)
 {
  //按下Translate按鈕之後 , 執行翻譯的動作
  try
  {
   EditTextTo.setText(Translate.execute(EditTextFrom.getText().toString(), LanguageCode[positionFrom], LanguageCode[positionTo]));
   
   int intLocal = TToSpeech.isLanguageAvailable(LocaleCode[positionFrom]);
   
   if(intLocal == TextToSpeech.LANG_MISSING_DATA || intLocal == TextToSpeech.LANG_NOT_SUPPORTED)
   {
    Toast.makeText(this, "不支援這個語言或是資料遺失", Toast.LENGTH_LONG).show();            
   }
   else
   {
    TToSpeech.setLanguage(LocaleCode[positionFrom]);
    TToSpeech.speak(EditTextFrom.getText().toString(), TextToSpeech.QUEUE_ADD, null);
      //發音 , 如果現在TTS Engine 正在發音的話 , 參數 TextToSpeech.QUEUE_FLUSH 代表會將其發音內容中斷 , 並且開始發音
      //TextToSpeech.QUEUE_ADD 代表會等到現在的發音的內容結束才開始發音      
   }
   
  } catch (Exception e)
  {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 @Override
 public void onInit(int status)
 {
  // TODO Auto-generated method stub
  Log.d("onInit" , "onInit");
 }
 
 @Override  
  protected void onDestroy()
 {  
     // TODO Auto-generated method stub  
     super.onDestroy();  
     TToSpeech.shutdown();  //釋放 TTS Engine所使用的資源
  }  
}


不過當按下Button的時候 , 要隔一兩秒才會發音 , 另外官網API Referece 的 Locale Class , 所提供的地區參數蠻多的 , 另外 TextToSpeech.OnInitListener interface的介紹 , 但是敘述的不多





看來不支援日文發音 , 就brother這個字來說 , 使用英文 , 德文和法文都可以成功發音


2010年12月7日 星期二

[Android] Android 2.3 Gingerbread Release




消息傳了那麼久終於在今天發佈了 , Android 2.3 Gingerbread 了 , 快去更新吧!!!!
這邊是AndroidUsersGuide-2.3 線上版pdf使用者手冊 , Android 2.3 Platform Hihglight 還針對使用者和開發者 , 新平台做了重點整理 ,  還有Demo的影片 , 如果不想看英文的話可以參考Andy Yang的這篇文章 他針對了幾個部分做了中文翻譯 , 可以搭配 Android 2.3 Platform Hihglight 文章內的圖片,  這樣會比較清楚 , copy/paste的功能終於改進了....ios都可以這樣使用很久了 , 還有些重點部分不知道可不可以在模擬器上測得出來....我開了GOOGLE API LEVEL 9 的模擬器 , 不過運作起來沒有很Smooth , 怪怪




裡面很多icon和介面都換過了 , 硬是要綠配黑....


另外對開發者來說很重要的部分是 , 更新了哪些API? 加入了哪些Package?
這邊有詳細的說明: Android API Differences Report , 希望之前遇到的Bug能藉由這次改版修復....

現在要使用到Android 2.3除非買 Google Nexus S , 而果真也是和之前傳聞的由三星代工


不過看起來還蠻有質感的 , 不知道等我當完兵會出到Nexus多少 哈


如果你使用Eclipse作為開發工具的話 , 可以參考這篇文章做更新(我是使用這個方法) , 成功之後Andorid SDK路徑的資料夾會多出你更新所下載的檔案 , 像我的Android_SDK\platforms\android-9 多出這個資料夾 , 我還看到Sumsung Galaxy Tab的元件可以下載...., 而這裡也可以直接下載Andorid 2.3 SDK Package




Andorid 2.3終於出了 , 不過我還在寫Android 2.2的case....



2010年12月5日 星期日

[Android] 如何發送簡訊並使用自訂的AlertDialog (How to send SMS and use custom AlertDialog)



就如標題說個該如何使用Android發送簡訊? 而對於使用自訂的AlertDialog , Android的Dev Guide有篇文章講解的很不錯 , 讓AlertDialog不再那麼單調了。


首先是程式碼的部分


package com.android;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;

public class SMSAlertDialog extends Activity implements Button.OnClickListener
{
    /** Called when the activity is first created. */
    
 @Override
    public void onCreate(Bundle savedInstanceState)
 {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ButtonContact = (Button) findViewById(R.id.ButtonContact);
        ButtonContact.setOnClickListener(this);
        
        EditTextMS = (EditText) findViewById(R.id.EditTextMS);
  
    }
 
 AlertDialog alertDialog;
 Button ButtonContact , ButtonOk , ButtonCancel;
 EditText EditTextMS , EditTextNumber;

 @Override
 public void onClick(View ButtonView)
 {
  // TODO Auto-generated method stub
  
  switch(ButtonView.getId())
  {
      case R.id.ButtonContact:
      showAlertDialog();
         break;
         
         case R.id.ButtonOk:
         SmsManager smsManager = SmsManager.getDefault();
         //想獲得SmsManager物件必須呼叫static method SmsManager.getDefault().
         //API Level 4 開始使用 android.telephony.SmsManager裡的 SmsManager.getDefault();
            //API Level 1的android.telephony.gsm.SmsManager已不被推薦使用

      PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
      //getBroadcast(Context context, int requestCode, Intent intent, int flags)
      //關於flags的說明可以參考官方的API Doc
      //http://developer.android.com/reference/android/app/PendingIntent.html
      smsManager.sendTextMessage(EditTextNumber.getText().toString(), null, EditTextMS.getText().toString(), pendingIntent, null);
      //傳送SMS
      Log.d(EditTextNumber.getText().toString(), EditTextMS.getText().toString());
      alertDialog.dismiss();      
      
      break;
         
         case R.id.ButtonCancel:      
            alertDialog.dismiss();            
      break;
  }
  
 }

 private void showAlertDialog()
 {
    AlertDialog.Builder builder = new AlertDialog.Builder(this);   
    LayoutInflater inflater = (LayoutInflater) this.getSystemService(LAYOUT_INFLATER_SERVICE);

    final View layout = inflater.inflate(
     R.layout.customalertdialog, (LinearLayout) findViewById(R.id.layout_root));
    //將customalertdialog.xml中的 R.id.layout_root的LinearLayout並return 一個View
   
    ImageView ImageViewPhone = (ImageView) layout.findViewById(R.id.ImageViewPhone);
    //因為要取出LinearLayout裡的元素 , 所以 要使用layout來呼叫findViewById(int R.id) method
    //否則會出現NullPointerException    
    ImageViewPhone.setImageResource(R.drawable.phone);
    
    EditTextNumber = (EditText) layout.findViewById(R.id.EditTextNumber);
       
    ButtonOk = (Button) layout.findViewById(R.id.ButtonOk);
    ButtonOk.setOnClickListener(this);
    ButtonCancel = (Button) layout.findViewById(R.id.ButtonCancel);
    ButtonCancel.setOnClickListener(this);
    
    builder = new AlertDialog.Builder(this);   
    builder.setTitle("連絡人號碼");
    builder.setView(layout);
    
    alertDialog = builder.create();
    //當一切都設置完成之後才可以create()
    alertDialog.show();
    //顯示AlertDialog
 }
}


由於我把custom AlertDialog的Layout存在另一個檔案customalertdialog.xml , 以下是他的內容



















接下來是main.xml的內容










最後只要記得在AndroidManifest加上使用者權限就ok了







連絡人號碼我使用5556 , 是代表另一個模擬器的名稱 , 而我在IDEOS上測試過了也沒問題


這個5556模擬器已經收到來自剛剛5554模擬器發出的簡訊了


2010年12月3日 星期五

[Android] How to save primitive data by SharedPreferences


對於一個應用程式來說有時候會需要記錄使用者的一些設定 , 例如玩遊戲的分數記錄或是靜音是否開啟之類的訊息 , 在Android Developers的網頁有一篇關於Data Storage的文章 , 裡面了介紹了幾種關於儲存資料的方法 , 如果只是要儲存一些primitive data ex. boolean , string , long , int之類的話 , 可以使用SharedPreferences方便多了 , 而當使用者離開程式或是被迫中斷的時候 , 我們必須做儲存資料的動作 , 而等到使用者再執行程式時我們再把資料取出來 , 這邊可以參考 Activity Lifecycle , 來決定override哪些函式達到資料 存 取的目的。




首先是程式碼的部分 , 簡單的用來記錄一個CheckBox勾選的狀態


package com.android;

import android.app.Activity;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.CheckBox;

public class TestDec extends Activity
{
    
 /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        CB = (CheckBox) findViewById(R.id.CheckBox);
    }
    
    boolean CheckBoxStatus;
    CheckBox CB;
    final String PreferencesName = "urPreferencesName";
    
    @Override  
    protected void onStart()
    {
     super.onStart();
     
     SharedPreferences settings = getSharedPreferences(PreferencesName, MODE_PRIVATE);
        //透過Context類別下的 getSharedPreferences(String name, int mode) method 
     //取出 名為name 的 SharedPreferences , 如果不存在的話會幫你create一個
     //mode是存取的權限 , 預設為MODE_PRIVATE : 只有此應用程式可以存取
     
     CheckBoxStatus = settings.getBoolean("CheckBoxStatus", false);
        //從中取出名為CheckBoxStatus的布林值 , 如果不存在的話預設傳回false

        CB.setChecked(CheckBoxStatus);
    }
    
    @Override  
    protected void onStop()
    {
     super.onStop();
     
     SharedPreferences settings = getSharedPreferences(PreferencesName, MODE_PRIVATE);
        SharedPreferences.Editor editor = settings.edit();
        editor.putBoolean("CheckBoxStatus", CB.isChecked());
        //名稱同樣為 CheckBoxStatus
        
        editor.commit();
    }
}

這麼一來當使用者按back鍵離開之後也不會遺失資料了

2010年12月1日 星期三

[雜談] 曼秀雷敦 - Lip Ice 水果潤唇膏




最近在電視上看到這個30秒的廣告 , 被廣告的音樂所吸引了 , 進一步來說應該是被陳奕迅的歌聲所吸引了~~~廣告的輕鬆風格再配上Eason的歌聲真是令人感到舒服愉快 , 雖然我平常沒有買潤唇膏的習慣 , 但是每次看到這支廣告都會看完 , 這支廣告分為國語和粵語的版本 , 但由於Eason在廣告裡本來就是唱英文的所以差別在最後配音人員說的話。




台灣版本的廣告 - 有搭配中英字幕







粵語版本的廣告







拍攝的幕後花絮 - 看來是在香港拍的廣告 , 工作人員都是說粵語







這邊有官方網站的抽獎活動(至12 / 24日截止)








Eason so great!!!!



後來又找到了其他的版本 , 合唱版結尾還有張柏芝 0.0







這個又勾起我的回憶了 , 金城武以前也代言過








Google Analytics