顯示具有 Android 標籤的文章。 顯示所有文章
顯示具有 Android 標籤的文章。 顯示所有文章

2012年10月3日 星期三

[Android] 如何使用 PreferenceActivity 及宣告Preference.xml (How to use PreferenceActivity and define Preference.xml)





        如果你想在app 中加入Setting 的功能讓使用者可以修改app 的功能和行為, 甚至將Setting value同步到 System Setting , Preference APIs 是一個不錯的選擇, 它的UI呈現方式就像 ListView 一樣, 而每個在ListView 中的subView 都可以宣告一個Key 為的是可以在SharedPreference 中存取資料, 如此一來Preference API 就會自動的幫我們儲存每個subView資料的變化, 讓我們省了不少功夫。


首先第一步要做的就是定義 XML, 將Setting 中會出現的各種Preference object 使用階層式的方式在XML 中定義清楚, 我們在 res/xml 中產生一個 XML 檔案preference.xml 如下:




在XML 檔案中 root element 必定為 PreferenceScreen, 代表著一個全新的Setting 畫面並且在其中加入我們所需要的Preference objects, 通常我們只會用到一個XML 檔案, 因為如果有需要新的Setting 畫面需求, 可以在root PreferenceScreen 中加入另一個PreferenceScreen 形成巢狀Setting。





我們使用Eclipse editor 的 Structure mode來編輯preference.xml, 這樣對於preference.xml 階層式的架構就非常清楚了, 所有的 Preference objects 都在root Preference中, 另外還加入了一個Second PreferenceScreen, 當使用者點選時會跳出一個新的Setting 畫面, 無需create 一個額外activity。

接下來介紹幾個常用的 attribute:





android:key
最重要的屬性, 必須是一個唯一的Key供SharedPreference 儲存setting value使用, 之後在PreferenceActivity 中我們也是靠Key 來識別不同的Preference object。
android:title
Preference object 出現在Setting 畫面中的名稱。
android:defaultValue
  SharedPreference 中儲存的初始值

android:summary
  Preference object 出現在Setting 畫面中的說明文字, 以較小的字出現在title 下方。



接下來先附上完整的 preference.xml, 再來針對每個Preference object 作介紹




    
        
    
        
        
    
    
        
    
    
        
        
          




EditTextPreference : 當使用者選擇時會跳出Dialog, 而關於這個Dialog 的屬性可以使用 Editor 事先編輯, 特別的是EditText 能宣告的屬性, EditTextPreference 也能拿來使用, 所以在這邊宣告了 inputType 屬性為只能輸入Decimal number, 另外我們將使用者輸入的數字顯示在summay 中, 不過這邊需要在PreferenceActivity 中加入此功能, 這部分稍後會提到。




ListPreference : 顯示一組有著Radio button 的List, 要使List 能呈現內容的話, 要事先為List import Entry array和Entry value array, 我們可以在 /res/value 中宣告 string array 再使用Editor 引入即可, array 內容如下:


    PreferenceActivity
    Hello world!
    Settings
    MainActivity
         
        First item    
        Second item    
        Third item    
     
        
        0    
        1    
        2    
     


Entry 宣告了要顯示在List 中的內容, Entry value 對應為每個 Entry 內容所代表的value
另外值得一提的是因為選擇ListPrefence時會跳出Dialog 所以如果在Editor 中再宣告Dialog Message 屬性時就會導致顯示內容錯誤, 另外Dialog 只會出現Negative Button !





RingtonePreference : 讓使用者選擇手機上的鈴聲, 將被選擇鈴聲的URI 以string 格式儲存在SharedPreference 中, showSilent 屬性表示列表中是否包含靜音的選項, ringtoneType 用來限制列表中顯示的鈴聲類型。


PreferenceCategory : 單純的將Prefence object 分類而已, 在針對不同的Category 內加入Preference object, 可以看到畫面上的"第一個分類", "第二個分類"....均是。


Preference : 更單純, 通常只是被用來當作"說明"功能而已。



稍微簡介了用到的Preference Objects 之後, 開始來看PreferenceActivity.java 程式主要在處理每個Preference object 初始的Summary 設定, 和註冊value change 的事件。



package com.example.preferenceactivity;


import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceActivity;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceScreen;
import android.preference.RingtonePreference;
import android.view.Menu;


public class MainActivity extends PreferenceActivity implements OnPreferenceChangeListener
{
 
 EditTextPreference editTextPreference, secondEditTextPreference;
 ListPreference listPreference;
 RingtonePreference ringtonePreference;
 
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preference); // 記得替換掉預設的 setContentView(R.layout.activity_main) 
    
        editTextPreference = (EditTextPreference) findPreference("EditTextP");
  editTextPreference.setOnPreferenceChangeListener(this);
  //替Preference註冊value改變事件, 因為我們要動態的更新Summary content
  
  editTextPreference.setSummary("EditTextPreference Value:" +editTextPreference.getText());
  
  listPreference = (ListPreference) findPreference("ListP");
  listPreference.setOnPreferenceChangeListener(this);
  listPreference.setSummary("ListPreference Value:"+ listPreference.getEntry());
  
  ringtonePreference = (RingtonePreference) findPreference("RingtoneP");
  ringtonePreference.setOnPreferenceChangeListener(this);
  
  //---In the second Screen
  
  secondEditTextPreference = (EditTextPreference) findPreference("SPCEditTextP");
  secondEditTextPreference.setOnPreferenceChangeListener(this);
  secondEditTextPreference.setSummary("EditTextPreference Value:" +secondEditTextPreference.getText());  
  
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }       

 @Override
 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
   Preference preference)
 {
  // TODO Auto-generated method stub
  return super.onPreferenceTreeClick(preferenceScreen, preference);
 }

 public boolean onPreferenceChange(Preference preference, Object newValue)
 {
  // TODO Auto-generated method stub  
   
  if(preference.getKey().equals("EditTextP")) //根據不同的key來辨別不同的Preference
   preference.setSummary("EditTextPreference Value:" +newValue);
  else if(preference.getKey().equals("ListP"))
   preference.setSummary("ListPreference Value:" +((ListPreference) preference).getEntries()[Integer.parseInt(newValue.toString())]);
      //從Entry中對應顯示使用者選擇的item value
  else if(preference.getKey().equals("RingtoneP"))
   preference.setSummary("RingtonePreference Value:" +newValue);
  else if(preference.getKey().equals("SPCEditTextP"))
   preference.setSummary("EditTextPreference Value:" +newValue);
  
  return true; 
 }
}


最後當我們成功儲存了每個Preference objects value 後, 該透過甚麼方法取得呢? 

網路上建議的作法:

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);

return value preferences 為 context 所使用的preference file, 接下來就可以對 preferences 進行數值的存取了。


相關文章:



2012年9月29日 星期六

[Android] 使用 TextSwitcher 切換ListView的內容 (How to use TextSwitcher change ListView content)



    
    透過TextSwitcher 的切換來改變ListView 的內容, 並且在切換的同時加入Animation 動畫. 然後使用Typeface將TextView的字體改變, 意即使用custom Font. 而為了要使用custom Font需將字體檔 wt021.ttf 事先匯入 assert/font folder, 到時候再透過 Typeface.createFromAsset(getAssets(), "fonts/wt021.ttf") 取用, 特別注意要加上副檔名 .ttf



首先來看 MainActivity的部分,



package com.example.textswitcher;

import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.Menu;
import android.view.View;
import android.view.animation.AnimationUtils;
import android.widget.ListView;
import android.widget.TextSwitcher;
import android.widget.TextView;
import android.widget.ViewSwitcher.ViewFactory;

public class MainActivity extends Activity
{

    TextSwitcher TSwitcher;
    static int textIndex = 0;
    
    String[] TSwitcherContent;
    ListView LView;
    TextArrayAdapter LAdapter;
    
    Typeface font;
    
 
 @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        font = Typeface.createFromAsset(getAssets(), "fonts/wt021.ttf");
        //從 assert folder 中取用我們匯入的自訂字體 wt021.ttf  
        
        iniTexTSwitcher();
        iniListView();
    }
    
 private void iniTexTSwitcher()
    {
  // TODO Auto-generated method stub
     
  TSwitcher = (TextSwitcher) findViewById(R.id.textSwitcher);
  
  //透過 setFactory method 產生兩個 Views給 TextSwitcher切換
     TSwitcher.setFactory(new ViewFactory(){
      
      //實作makeView() method 產生 View供TextSwitcher作切換 
      public View makeView()
      {       
       TextView tv =new TextView(MainActivity.this);
       tv.setTypeface(font); // 使用自訂的字體
       tv.setTextSize(40);
       tv.setTextColor(Color.GREEN);       
       tv.setGravity(Gravity.CENTER);           
       return tv;
      }
     });
     
     TSwitcherContent = getResources().getStringArray(R.array.textSwitcherContent);
     //將事先定義在 /res/value 的string array 取出使用
     
     TSwitcher.setText(TSwitcherContent[textIndex]);      
     
     //替 TextSwitcher註冊 onClickListener
     TSwitcher.setOnClickListener(new View.OnClickListener()
     {
      public void onClick(View v)
      {       
       textIndex++;
       
       if (textIndex >= TSwitcherContent.length) textIndex = 0;
         
       TSwitcher.setText(TSwitcherContent[textIndex]);       
       
       LAdapter.notifyDataSetChanged();
       //告知Adapter的data 已經改變了, 所以我們手動的呼叫 notifyDataSetChanged() method
       
       LView.startAnimation(AnimationUtils.loadAnimation(getApplicationContext(), android.R.anim.fade_in));
       //當TextSwitcher切換時, 讓ListView有淡入的效果
      }
     });      
      
     TSwitcher.setInAnimation(AnimationUtils.loadAnimation(getApplicationContext(), android.R.anim.slide_in_left));       
     // TextSwitcher切換時也使用動畫效果
 }

 private void iniListView()
    {
  // TODO Auto-generated method stub  
  
  LView = (ListView) findViewById(R.id.listView);
  
  LAdapter = new TextArrayAdapter(MainActivity.this, android.R.layout.simple_list_item_1, TSwitcherContent, font);
  //宣告使用custom TextArrayAdapter that extends ArrayAdapter
  //android.R.layout.simple_list_item_1, 為android system resource layout 只能顯示single line, 所以本身即為一個TextView  
  
  LView.setAdapter(LAdapter);  
 }

 
 @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}


接著來談一下TextSwitcher吧, TextSwitcher的父類別為 ViewSwitcher, 所以具有和ViewSwitcher共同的性質, ViewSwitcher 透過 setFactory(new ViewFactory()) 產生兩個child views給ViewSwitcher作切換, ViewSwitcher 只能夠有兩個 child views, 並且一次只能顯示一個, 另外 ViewSwitcher 有兩個子類別, 一為TextSwitcher 另一個為 ImageSwitcher, 差別在於TextSwitcher 所包含的 child views 只能為 TextView type的 view, ImageSwitcher 只能擁有 ImageView type的 child view



處理完畢 MainActivity.java 的內容之後我們來看 ListView, 首先ListView 和 data 之間必須有Adapter作為配接, 在這邊我們使用custom Adapter that extends ArrayAdapter, 我已經將這個custom adapter 取名為 TextArrayAdapter, 並且獨立一個.java 如下


package com.example.textswitcher;

import android.app.Activity;
import android.content.Context;
import android.graphics.Typeface;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class TextArrayAdapter extends ArrayAdapter
{

 Activity mainActivity;
 Typeface itemFont;
 
 public TextArrayAdapter(Context context, int textViewResourceId, String[] objects, Typeface font)
 {
  super(context, textViewResourceId, objects);
  // TODO Auto-generated constructor stub
  
  mainActivity = (Activity) context;
  //特別取得 MainActivity
  
  itemFont = font;
 }

 @Override
 public View getView(int position, View convertView, ViewGroup parent)
 {
  // TODO Auto-generated method stub
  
  View rowview = convertView;
        if (rowview == null) {
                LayoutInflater inflater = mainActivity.getLayoutInflater();
                rowview = inflater.inflate(android.R.layout.simple_list_item_1, parent, false);
                //為了要把xml所描述的Layout轉變為View , 也就是我們要的rowview
                //所以必須使用LayoutInflater來轉化 , 而要取得LayoutInflater的Instance方式還包括
                //LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                //接著再使用inflate (int resource, ViewGroup root, boolean attachToRoot)
                //傳回我們要的rowview
        }
        
        TextView item = (TextView) rowview;
        
        item.setTypeface(itemFont);
        item.setGravity(Gravity.CENTER);
        
        //根據當前的 textIndex 來決定ListView該呈現的內容
        switch(MainActivity.textIndex)
        {
         case 0:
          item.setText("First");
         break;
         
         case 1:
          item.setText("Second");
         break;
         
         case 2:
          item.setText("Third");
         break;
         
         case 3:
          item.setText("Fouth");
         break;        
        }
  
  return rowview;
 } 

}



最後是簡單的 activity_main.xml


    
    

    

    






2012年4月8日 星期日

[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年3月28日 星期三

[Android] 很簡單的使用搜尋功能 Use Global Search





提到使用Search Interface 去搜尋我們要的資料, 可搜尋的範圍是根據我們程式提供多少資料來源, 例如我們去查詢的資料來源為手機中的通訊錄, 簡訊夾, 或是一些應用程式, 大部分的資料來源都是從ContentProvider取得的, 而根據使用者的需要甚至可以擴大到當做翻譯字典甚至網路搜尋引擎, 不過包含的內容越多程式就越複雜, 那Android有提供方法讓我們不用在我們的App中撰寫複雜的程式碼卻也可以達到同樣搜尋的功效嗎?

很開心的是 Android的確有提供這樣的辦法協助開發人員輕鬆的使用這些搜尋功能, 它就是Global Search, 接下來看看一個簡單的使用範例。

package COM.TQC.GDD03;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;


public class GDD03 extends Activity
{  
  /** Called when the activity is first created. */
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);    
    
    setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL);
    //在鍵盤上打字時會自動的啟動Search UI, 並且預設為Global Search 
   
  }
  
  @Override
  public boolean onSearchRequested()
  {
 // TODO Auto-generated method stub  
     
  Log.d("Test","onSearchRequested()"); 
     
     startSearch("請輸入聯絡人姓名", false, null, true);
     /* 第四個參數代表是否啟用Global Search 功能, 在這個範例我們選擇 true
      * 如果要使用自定義的Local Search的話就代入 false
      */
  return true;  
  }
}


另外使用Global Search的功能是不用像Local Search那樣還需要額外的定義一個  searchable.xml , 也不用在AndroidManifest.xml中去額外的幫Activity定義 Search Action 和 searchable 的resource, 但是我們仍然能使用SEARCH BUTTON呼叫 Search UI, 也能夠讓 
onSearchRequested() Method 起作用, 這都是因為Global Search 的關係, 另外有一個值得一提的地方是使用鍵盤輸入會喚起Search UI, 但是從Log訊息中可以發現這個動作是不會去call  onSearchRequested() method 的(無論是Global Search or Local Search), 所以就算拿掉 override onSearchRequested() method的部分, 仍然可以靠 setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL)呼叫 Search UI 成功的完成Global search的動作。









2011年6月1日 星期三

[TQC+ Android] Android 認證/證照 - TQC+ Android




今天看到一則關於Android證照的新聞 , 當兵前就在尋找關於Android認證的資訊 , 但是那時候沒有相關的消息 , 不過現在國內電腦技能基金會已經開始在推廣Android的證照了 - TQC+ Android , 可以在TQC+的網站看到相關的消息  , 而且看起來Android的認證還分為兩個等級分別是 行動裝置應用程式設計(Android 2) / 行動裝置進階應用程式設計(Android 2), 報名費是1500 / 1800元 , 再仔細看了一下 , 連參考書都出了"TQC+ 行動裝置應用程式設計認證指南-Android 2"....對於以後想走Android這條路的同志們有了一個新的準備方向了 
這本書我有購入 , 內容以為會像SCJP的猛虎出閘類似 , 對於Andorid的內容會有章節的介紹再附上題庫 , 沒想到........只有題庫 , 分為測驗題和操作題 , 測驗題的題型是單選題和複選題 , 操作題就是程式實作題 , 而這項測驗總共120分鐘總分100分 , 測驗題20題作答時間20分鐘每題1分答錯倒扣 , 操作題3題共80分 , 兩個大題要共拿70分才算及格

操作題從基礎到進階 , 不少題目蠻有水準的 , 出題內容不侷限於你我平常所讀的教學書籍 , 有些題目的解題必須多閱讀Android tutorials和網路上許多教學文章 , 之後會在網頁上po一個TQC+ 操作題系列文章和大家分享

關於Android的技能認證 , 目前為止還沒有推出國際證照 , 我當初以為Google會開始動作 , , 但是國外許多Android的程式開發者有在Google的開發者論壇詢問 , 目前還沒獲得官方的回應 , 不過想了一下Android的版本更新速度太快了 , 如果以版本為區隔推出證照那要考多少次呀....我拿到SCJP5.0 就不會想再去考6.0了....




2011年1月3日 星期一

[Android] 如何藉由Cell ID 和 Location Area Code 取得經緯度座標 (How to get Cell ID and Location Area Code by Geolocation API)



在之前的文章 [Android] 如何使用Google Map 2.0 & 3.0 中有提到 , 當取得Cell Id 和LAC 之後可以透過 http://www.google.com/glm/mmap 這個API取得經緯度 , 不過在網路上關於這個API的Reference真的很少 , 根據目前我所蒐集的資料 , 只知道可以丟入Cell Id 和LAC 作為實際能影響經緯度的輸入參數 , 而網路上另外還有一個類似的API : Geolocation API , 這網頁有介紹API的Network Protocal , 描述了參數輸入的格式和回報輸出的經緯度格式 , 並針對每個參數做敘述 , 值得一提的是並不是每個參數都是必要輸入的。


另外可以看到如果使用字串來編排作為參數的輸入格式會相當的麻煩 , 在這篇文章會使用java 的 JSON物件來作為參數的輸入格式 , 而其中主要會影響輸出經緯度的輸入的參數為 cell_towers 和 wifi_towers , 有趣的是參數的內容都可以為多個 , 這樣可以增加輸出經緯度的準確度 , 這也是為什麼這個API是網路上比較多人使用的原因。


最後我們會使用TabWidget分頁來呈現要送出的參數和回應的資料 , 以下是程式碼的部分


import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import android.app.TabActivity;
import android.content.res.Configuration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;
import android.widget.TabHost;
import android.widget.TextView;


public class LocationTest extends TabActivity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mTabHost = getTabHost();
        //我們的TabWidgetListViewTest 是繼承 TabActivity的
        //所以這邊是透過他的method getTabHost()
        //來取得我們在main.xml中宣告的TabHost
         
        mTabHost.addTab(mTabHost.newTabSpec("tab_test1")
          //將Tab加入我們要的資料
          .setIndicator("Send JSON Object")
          //在Tab上標示名稱和加上Icon
          .setContent(R.id.LinearLayoutJSON));
                //Tab內容為main.xml檔案中宣告的ListView , 而ListView的內容我們先前已經處理好了
         
        mTabHost.addTab(mTabHost.newTabSpec("tab_test2").setIndicator("Response").setContent(R.id.LinearLayoutRES));
                 
        mTabHost.setCurrentTab(0);
        //預設該顯示哪個Tab的資料
        
        InitialUI(); //初始化元件
        getInfo();   //取得mmc mnc的資訊    
        getLocation();
    }    
 
    int CellID , LAC , mcc , mnc;    
    double lat, lng;
    
    TelephonyManager TManager;   
    WifiManager WManager;
    GsmCellLocation GCLocation;
 TextView TextViewJSON , TextViewRES;
    
    private void getInfo()
 {
       Configuration CF = (Configuration) getResources().getConfiguration(); 
     if(CF != null)
     {
     mcc = CF.mcc;
       mnc = CF.mnc; 
     }
 }
    
    private void InitialUI()
    { 
      TextViewJSON = (TextView) findViewById(R.id.TextViewJSON);
      TextViewRES = (TextView) findViewById(R.id.TextViewRES);
      
   TManager = (TelephonyManager) this.getSystemService(this.TELEPHONY_SERVICE); 
   WManager = (WifiManager) this.getSystemService(this.WIFI_SERVICE);
    }
        
    private void getCellIdLAC()
    {  
   if(TManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM)
        { 
        GCLocation = (GsmCellLocation) TManager.getCellLocation();
        //String imei = TManager.getDeviceId();
        if (GCLocation != null)
           {          
         CellID = GCLocation.getCid();
                  LAC = GCLocation.getLac();
              }
           else TextViewRES.append("無法獲得 GsmCellLocation 物件");
        }
        else TextViewRES.append("這不是GSM手機"); 
   }
    
    private void getLocation()
    {
  // TODO Auto-generated method stub
     
       JSONObject Locationdata = null;
     
       try
       {
      JSONObject JSONholder = new JSONObject();
      
      JSONholder.put("version", "1.1.0");
      JSONholder.put("host", "Android");
      JSONholder.put("request_address", true); //需要回報地址
      JSONholder.put("home_mobile_country_code", mcc);
      JSONholder.put("home_mobile_network_code", mnc);
      JSONholder.put("radio_type", "gsm"); //string (gsm|cdma|wcdma)     
      JSONholder.put("address_language", "zh_TW"); //地址語言 
           
           JSONArray cellTowerArray = new JSONArray(); 
           //為了可以儲存多筆Cell Tower的資料
           getCellIdLAC(); //取得Cell ID 和 LAC的資料     
         
           JSONObject cellTowerData = new JSONObject();
           cellTowerData.put("cell_id", Integer.toString(CellID)); 
      cellTowerData.put("location_area_code", LAC); 
      cellTowerArray.put(cellTowerData); // 目前只放入一筆資料 
      JSONholder.put("cell_towers", cellTowerArray);      
     
      
      JSONArray wifiTowerArray = new JSONArray();       
           JSONObject wifiTowerData = new JSONObject();
               
           WifiInfo WI = WManager.getConnectionInfo(); //取得現在連線的AP資訊               
           wifiTowerData.put("mac_address", WI.getMacAddress());
            wifiTowerData.put("ssid", WI.getSSID());
            wifiTowerArray.put(wifiTowerData);
           
         JSONholder.put("wifi_towers", wifiTowerArray);
        
      TextViewJSON.setText(JSONholder.toString());
      //顯示參數的輸出格式
      
         String urlString = "http://www.google.com/loc/json";          
         DefaultHttpClient client = new DefaultHttpClient();
         HttpPost post = new HttpPost(urlString); //使用post協定           
         StringEntity se = new StringEntity(JSONholder.toString());
         post.setEntity(se);
         HttpResponse resp = client.execute(post);
         HttpEntity entity = resp.getEntity(); //取得API傳回的資料          
           
         String result = EntityUtils.toString(entity); //轉成字串格式
         
         JSONObject RESdata = new JSONObject(result);
         //以下為解析回報得資料
         Locationdata = (JSONObject) RESdata.get("location");
         lat = (Double) Locationdata.get("latitude");
           lng = (Double) Locationdata.get("longitude");          
           
           JSONObject Addressdata = (JSONObject) Locationdata.get("address");
           StringBuffer sb = new StringBuffer();
           sb.append(Addressdata.get("country")+" ");
           sb.append(Addressdata.get("postal_code")+" ");
           sb.append(Addressdata.get("region")+" ");
           sb.append(Addressdata.get("city")+" ");
           sb.append(Addressdata.get("street")+" ");          
           
           TextViewRES.setText(lng+","+lat+" "+sb.toString());
           TextViewRES.append("\n\n"+Locationdata.toString());
         
         }catch(Exception E)
         {
          E.printStackTrace();
          Log.v("Locaiton result", "SomethingWrong"); 
          TextViewRES.setText("SomethingWrong ");
          TextViewRES.append(lng+","+lat+" "+Locationdata.toString());
         }  
 }
 TabHost mTabHost;
}


再來是Layout檔


    
        
         
        
        
                
        
        
        
                
         
            
        
        
               
        
        
        
                
                  
        
                  
      

最後是AndroidManifest
    
        
            
                
                
            
        
    
    
 
 
 
  

最後就可以獲得經緯度和地址的資訊了 , 實作結果如下



這邊呈現出準備要送出的JSON物件裡的內容




這邊為回應的資料結果




相關文章:
[Android Tips] 如何使用Google Map 2.0 & 3.0

[Android Tips] 如何取得手機的Cell ID 和 Location Area Code


參考資料:
http://developer.clear.com/wiki/12/JSON_REST

2010年12月16日 星期四

[Android] 如何使用語音辨識功能 (Speech Recognition Method)



Android 是一個開放系統 , 所以可以在我們的應用程式中加入許多Google 的服務 , 例如這次的語音辨識服務 , 之前在這篇 : [Android] 如何使用Google Translate API 和 Spinner 製作翻譯機 也使用過翻譯服務 , 這個服務會使用到 RecognizerIntent , 而Google的語音搜尋程式通常都已經是先安裝在Android 的手機上了 , 可以透過 設定 > 應用程式 > 管理應用程式 做檢查 , 像遠傳小精靈IDEOS就安裝了TextToSpeech功能和語音搜尋功能。


Google為了讓語音辨識盡可能的準確 , 所以他分了很多語言模組 (Language Model) , 根據不同目的 , 不同的使用情況來使用 , 目前分了Free Form , 和 Web Search 兩個模組 , 前者適合長句子 , 後者適合短句子 , 而且是針對搜尋所會使用的詞彙去調整的 , 就端看程式人員認為該用在哪種情況囉。


Google的伺服器現在支援 , 英文 , 官方中文和日文 , 對於Free Form model 來說 , 英文版本有了最佳化 , 他們正開發更多的語言來支援這項服務 , 另外這個Method 模擬器沒辦法測試 , 下面的截圖是用IDEOS跑的。






請開啟網路 , 不然無法連線至伺服器


按下按鈕就等待使用者說話


辨識速度還蠻快的通常不到一秒


誤差很小喔


What......


講了幾個小數的運算 , 加減乘除沒出來



好玩 , 不過我是使用Free Form而最佳化目前只有英文 , 如果是用Web Search的話 , 中文的辨識度還蠻高的 , 雖然要連線但是我沒有註冊User-Permission , 程式碼就不貼啦 , 從這邊copy and paste就可以了 , Thanks Google!

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模擬器發出的簡訊了


Google Analytics