Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> 手機上的 Scala:使用 Android、Scala 和 Eclipse 創建移動應用程序

手機上的 Scala:使用 Android、Scala 和 Eclipse 創建移動應用程序

編輯:Android開發實例

  先決條件

  在本文中,我們將創建一個在 Android 設備上運行的移動應用程序。您將需要安裝 Android SDK;本文使用 V1.5 SDK。應用程序代碼將用 Scala 編程語言編寫。如果您從來沒用過 Scala,那麼沒有關系,因為本文將解釋 Scala 代碼。但是,即使您不熟悉 Scala,建議您至少熟悉 Java 語言。本文使用 Scala V2.7.5 進行開發。對於 Android 和 Scala 都提供了很好的 Eclipse 插件。本文使用 Eclipse V3.4.2 和 Android Development Tools(ADT) V0.9.1 以及 Scala IDE 插件 V2.7.5。

  設置

  編寫 Android 應用程序聽起來像是一個復雜的命題。Android 應用程序在它們自己的虛擬機中運行:Dalvik 虛擬機。但是,Android 應用程序的構建路徑是開放的。下面表明了我們將使用的基本策略。


圖 1. Android 上 Scala 的構建路徑


  其思想是,我們首先將所有 Scala 代碼編譯成 Java 類文件。這是 Scala 編譯器的工作,所以這方面沒什麼太復雜的事情。接下來,獲取 Java 類文件,使用 Android dex 編譯器將類文件編譯成 Android 設備上的 Dalvik VM 使用的格式。這就是所謂的 dexing,也是 Android 應用程序的常規編譯路徑。通常,要經歷從 .java 文件到 .class 文件再到 .dex 文件的過程。在本文,惟一不同的是我們從 .scala 文件開始。最後,.dex 文件和其他應用程序資源被壓縮成一個 APK 文件,該文件可安裝到 Android 設備上。

  那麼,如何讓這一切發生?我們將使用 Eclipse 做大部分工作。但是,此外還有一個較復雜的步驟:要讓代碼運行,還需要來自標准 Scala 庫中的代碼。在典型的 Scala 安裝中,這是 /lib/scala-library.jar 中一個單獨的 JAR。但是,這個 JAR 包括一些不受 Android 支持的代碼。有些代碼需要稍作調整,有些代碼則必須移除。scala-library.jar 的定制構建是運行得最好的,至少目前是這樣。我們將把這個 JAR 稱作 Android 庫 JAR。

  有了這個 JAR,剩下的事情就很容易了。只需使用 Eclipse 的 ADT 插件創建一個 Android 項目。然後將一個 Scala 特性(nature)添加到項目中。用前面談到的 Android 庫替代標准的 Scala 庫。最後,將輸出目錄添加到類路徑中。現在,可以開始了。主 Scala 站點對此有更詳細的描述。現在,我們有了基本的設置,接下來看看我們將使用 Scala 創建的 Android 應用程序。

  UnitsConverter

  現在,我們知道如何利用 Scala 代碼,將它轉換成將在 Android 設備上運行的二進制格式,接下來可以使用 Scala 創建一個移動應用程序。我們將創建的應用程序是一個簡單的單位轉換應用程序。通過這個應用程序可以方便地在英制單位與公制單位之間來回轉換。這是一個非常簡單的應用程序,但是我們將看到,即使是最簡單的應用程序也可以從使用 Scala 中獲益。我們首先看看 UnitsConverter 的布局元素。

  創建布局

  您也許對編寫手機上運行的 Scala 感到興奮,但是並非所有的移動開發編程都應該用 Scala 或 Java 語言完成。Android SDK 提供了一種很好的方式,使用基於 XML 的布局系統將用戶界面代碼與應用程序邏輯分離。我們來看看本文中的應用程序的主要布局文件,如清單 1 所示。

  以上代碼非常簡潔地創建了該應用程序的主 UI。它的根節點是一個 RelativeLayout 容器元素。Android SDK 中有很多布局選項。RelativeLayout 指示運行時使用相對定位對不同的 UI 小部件進行布局。要使用相對定位,可添加可見元素 — 在這裡是一個 TextView 元素。這是用於顯示文本的一個簡單的元素。它被賦予一個 ID prompt_label。接下來的元素,即一個 EditText 元素(一個文本輸入框)將用到它。這個元素有一個 layout_below 屬性,它的值等於 prompt_label ID。換句話說,EditText 應該放在名為 prompt_label 的元素的下方。

  布局代碼剩下的部分非常簡單。有一個帶標簽的文本輸入框、一個帶標簽的微調器(一個組合框或下拉框)、一個按鈕和一個用於輸出的文本框。圖 2 顯示正在運行的應用程序的一個截圖,其中標出了不同的元素。


圖 2. Android lLayout — 分解圖


  那麼,以上視圖中看到的不同文本值來自哪裡呢?注意,清單 1 中的一些元素有一個 text 屬性。例如,prompt_label 元素有一個等於 @string/prompt_metric 的 text 屬性。這表明它將使用 Android 應用程序中一個標准的資源文件:strings.xml 文件,如清單 2 所示。


清單 2. strings.xml 資源
<?xml version="1.0" encoding="utf-8"?> 
<resources> 
  <string name="prompt_metric">Enter amount (KM, g, L, C)</string> 
  <string name="prompt_english">Enter amount (miles, lbs, gallons, 
F)</string> 
  <string name="uom">Units of Measure</string> 
  <string name="convert_button_label">Convert</string> 
  <string name="app_name">Converter</string> 
  <string name="english_units">English</string> 
  <string name="metric_units">Metric</string> 
</resources> 

  視圖背後的代碼

  解釋創建 UI 的 Scala 代碼的最簡單方式是先看看代碼,然後走查一遍。對於任何應用程序,都是在應用程序的 AndroidManifest.xml 文件中定義應用程序的默認活動。任何 UI 背後都有一個 Activity 類,默認的 Activity 定義當應用程序初次裝載時執行的 Activity 類。對於像本文這樣簡單的應用程序,有一個 Converter 類,清單 4 中顯示了它的源代碼。


清單 4. Converter 活動類
class Converter extends Activity{ 
  import ConverterHelper._ 
  private[this] var amountValue:EditText = null 
  private[this] var uom:Spinner= null 
  private[this] var convertButton:Button = null 
  private[this] var resultValue:TextView = null 
   
  override def onCreate(savedInstanceState:Bundle){ 
   super.onCreate(savedInstanceState) 
   setContentView(R.layout.main) 
   uom = findViewById(R.id.uom_value).asInstanceOf[Spinner] 
   this.setUomChoice(ENGLISH) 
   amountValue = findViewById(R.id.amount).asInstanceOf[EditText] 
   convertButton = findViewById(R.id.convert_button).asInstanceOf[Button] 
   resultValue = findViewById(R.id.result_value).asInstanceOf[TextView] 
   convertButton.setOnClickListener( () => { 
     val unit = uom.getSelectedItem.asInstanceOf[String] 
     val amount = parseDouble(amountValue.getText.toString) 
     val result = UnitsConverter.convert(Measurement(unit,amount)) 
     resultValue.setText(result) 
   }) 
  } 
  override def onCreateOptionsMenu(menu:Menu) = { 
   super.onCreateOptionsMenu(menu) 
   menu.add(NONE, 0, 0, R.string.english_units) 
   menu.add(NONE, 1, 1, R.string.metric_units) 
   true 
  } 
  override def onMenuItemSelected(featureId:Int, item:MenuItem) = { 
   super.onMenuItemSelected(featureId, item) 
   setUomChoice(if (item.getItemId == 1) METRIC else ENGLISH) 
   true 
  } 
  private 
  def setUomChoice(unitOfMeasure:UnitsSystem){ 
   if (uom == null){ 
    uom = findViewById(R.id.uom_value).asInstanceOf[Spinner] 
   } 
   val arrayId = unitOfMeasure match { 
    case METRIC => R.array.metric_units 
    case _ => R.array.english_units 
   } 
   val units = new ArrayAdapter[String](this, R.layout.spinner_view, 
    getResources.getStringArray(arrayId)) 
   uom.setAdapter(units)    
  } 
} 

  如果用 Java 語言編寫,那麼必須傳入 Android 接口 OnClickListener 的一個實現。這個接口只定義一個方法:onClick。實際上,您關心的只是那個方法,但是在 Java 語言中無法直接傳入方法。而在 Scala 中則不同,在 Scala 中可以傳入方法字面量(literal)或閉包。在這裡,我們用語法 () => { ... } 表示閉包,其中方法的主體就是花括號中的內容。開始/結束括號表示一個不帶參數的函數。但是,我將這個閉包傳遞到 Button 的一個實例上的 setOnClickListener 方法,Button 是 Android SDK 中定義的一個 Java 類。如何將 Scala 閉包傳遞到 Java API?我們來看看。

  Android 上的函數式編程

  為了理解如何讓 Android API 使用函數字面量,看看 Converter 類定義的第一行。這是一條重要的語句。這是 Scala 的另一個很好的特性。您可以在代碼的任何地方導入包、類等,它們的作用域限於導入它們的文件。在這裡,我們導入 ConverterHelper 中的所有東西。清單 5 顯示 ConverterHelper 代碼。


清單 5. ConverterHelper
object ConverterHelper{ 
 import android.view.View.OnClickListener 
 implicit def funcToClicker(f:View => Unit):OnClickListener = 
  new OnClickListener(){ def onClick(v:View)=f.apply(v)} 
 implicit def funcToClicker0(f:() => Unit):OnClickListener = 
  new OnClickListener() { def onClick(v:View)=f.apply} 
} 

  這是一個 Scala 單例(singleton),因為它使用對象聲明,而不是類聲明。單例模式被直接內置在 Scala 中,可以替代 Java 語言中的靜態方法或變量。在這裡,這個單例存放一對函數:funcToClicker 和 funcToClicker0。這兩個函數以一個函數作為輸入參數,並返回 OnClickListener 的一個實例,OnClickListener 是 Android SDK 中定義的一個接口。例如,funcToClicker 被定義為以一個函數 f 為參數。這個函數 f 的類型為帶一個 View 類型(Android 中的另一個類)的輸入參數的函數,並返回 Unit,它是 void 在 Scala 中的對等物。然後,它返回 OnClickListener 的一個實現,在這個實現中,該接口的 onClick 方法被實現為將輸入函數 f 應用到 View 參數。另一個函數 funcToClick0 也做同樣的事情,只是以一個不帶輸入參數的函數為參數。

  Measurement 是一個 case 類。這是 Scala 中的一個方便的特性。用 “case” 修飾一個類會導致這個類生成這樣一個構造函數:這個構造函數需要類的屬性,以及 equals、 hashCode 和 toString 的實現。它對於像 Measurement 這樣的數據結構類非常適合。它還為定義的屬性(在這裡就是 uom 和 amount)生成 getter 方法。也可以將那些屬性定義為 vars(可變變量),然後也會生成 setter 方法。僅僅一行 Scala 代碼可以做這麼多事情!

  接下來,UnitsConverter 也是一個單例模式,因為它是使用 object 關鍵詞定義的。它只有一個 convert 方法。注意,convert 被定義為相當於一條單一語句 — 一條 match 語句。它是一個單一表達式,所以不需要額外的花括號。它使用 Scala 的模式匹配。這是函數式編程語言中常見的一個強大特性。它類似於 Java 語言和很多其他語言中的 switch 語句。但是,我們可以匹配字符串(實際上,還可以有比這高級得多的匹配)。如果字符串匹配,則執行適當的計算,並返回格式化的字符串,以供顯示。最後,注意與 _ 匹配的最後一個 case。Scala 中的很多地方使用下劃線作為通配符。在這裡,它表示匹配任何東西,這類似於 Java 語言中的 default 語句。

  現在,我們理解了應用程序中的計算,最後來看看剩下的 UI 設置和菜單。

  UI 初始化和菜單

  回到清單 4。我們說過要看看 setUomChoice。這個方法被定義為帶有一個 UnitsSystem 類型的參數。我們來看看如何定義這個類型。


清單 7. UnitsSystem
sealed case class UnitsSystem() 
case object ENGLISH extends UnitsSystem 
case object METRIC extends UnitsSystem 

  我們看到,UnitsSystem 是一個密封的 case 類,沒有屬性。看上去它不是很有用。接下來,我們看看兩個 case 對象。還記得嗎,object 表示 Scala 中的一個單例。在這裡,有兩個 case 對象,每個 case 對象都擴展 UnitsSystem。這是 Scala 中的一個常見的特色,它可以提供更簡單、更類型安全的枚舉方式。

  現在 setUomChoice 的實現更加合理。在獲得微調器的一個句柄後,我們匹配傳入的 UnitsSystem 的類型。這標識了我們在前面見到的 arrays.xml 中的一個數組。這是使用 Android SDK 生成的 R 類表示資源,例如 arrays.xml 文件。一旦知道使用哪個數組,我們就通過創建一個傳入微調器的適配器(在這裡是一個 ArrayAdapter),使用那個數組作為微調器的數據源。

  最後,看看清單 4 中的 onCreateOptionsMenu 和 onMenuItemSelected 方法。這些方法是在 Activity 中定義的,我們將在 Converter 活動中覆蓋這些方法。第一個方法創建一個菜單。第二個方法處理用戶從菜單中選擇 English 或 metric 的事件。它再次調用 setUomChoice。這使用戶可以在從英制單位轉換為公制單位與從公制單位轉換為英制單位之間進行切換。

  結束語

  Android 平台的架構使它可以用於在 Java 虛擬機上運行的任何編程語言。我們看到了如何設置 Android 項目,使它使用 Scala 代碼。這個過程也可以延伸到其他 JVM 編程語言,例如 Groovy、JRuby 或 Fan。當可以任意使用 Scala 編程語言時,編寫 Android 應用程序將變得更輕松。您仍可以使用 Eclipse 進行開發。仍然可以在 Eclipse 中用模擬器和設備進行調試。您可以繼續使用所有的工具,同時又得到一種生產率更高的編程語言。

  

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved