使用 Android、Scala 和 Eclipse 创建移动应用程序(2)

来源:developerWorks 中国 作者:Michael Galpin
  


Scala 应用程序代码

Converter 应用程序的代码非常简单 — 不管用什么语言编写。当然,用 Java 编写起来非常容易,但是用 Scala 编写也同样不复杂。首先我们看看前面见过的 UI 背后的代码。

视图背后的代码

解释创建 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)      
    }
}

我们从这个类的顶部开始。它扩展 android.app.Activity。这是一个 Java 类,但是从 Scala 中可以对 Java 类轻松地进行细分。接下来,它有一些实例变量。每个实例变量对应前面定义的一个 UI 元素。注意,每个实例变量还被限定为 private[this]。这演示了 Scala 中特有的一种访问控制级别,而 Java 语言中不存在这种访问控制。这些变量不仅是私有的,而且只属于 Converter 类的特定实例。这种级别的访问控制对于移动应用程序来说有些大材小用,但是如果您是一名 Scala 开发人员,可以放心地在 Android 应用程序上使用您熟悉的语法。

回到清单 4 中的代码,注意,我们覆盖了 onCreate 方法。这是 Activity 类中定义的方法,通常被定制的 Activity 覆盖。如果用 Java 语言编写该代码,那么应该添加一个 @Override 标注。在 Scala 中,override 是一个关键词,用于确保正确性。这样可以防止误拼方法名之类的常见错误。如果误拼了方法名,Scala 编译器将捕捉到方法名并返回一个错误。注意,在这个方法上,以及任何其他方法上,不需要声明返回类型。Scala 编译器可以轻松推断出该信息,所以不需要多此一举。

onCreate 中的大部分代码类似于 Java 语言编写的代码。但是有几点比较有趣。注意,我们使用 findViewById 方法(在 Activity 子类中定义)获得不同 UI 元素的句柄。这个方法不是类型安全的,需要进行类型转换(cast)。在 Scala 中,要进行类型转换,可使用参数化方法 asInstanceOf[T],其中 T 是要转换的类型。这种转换在功能上与 Java 语言中的转换一样。不过 Scala 有更好的语法。接下来,注意对 setUomChoice 的调用(稍后我们将详细谈到这个方法)。最后,注意上述代码获得一个在布局 XML 中创建的按钮的句柄,并添加一个单击事件处理程序。

如果用 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 也做同样的事情,只是以一个不带输入参数的函数为参数。

这两个函数(funcToClicker 和 funcToClicker0)都被定义为隐式函数(implicit)。这是 Scala 的一个方便的特性。它可以让编译器隐式地将一种类型转换成另一种类型。在这里,当编译器解析 Converter 类的 onCreate 方法时,它遇到一个 setOnClickListener 调用。这个方法需要一个 OnClickListener 实例。但是,编译器却发现一个函数。在报错并出现编译失败之前,编译器将检查是否存在隐式函数,允许将函数转换为 OnClickListener。由于确实还有这样的函数,所以它执行转换,编译成功。现在,我们理解了如何使用 Android 中的闭包,接下来更仔细地看看应用程序逻辑 — 特别是,如何执行单位转换计算。

单位转换和计算

我们回到清单 4。传入 onClickListener 的函数收到用户输入的度量单位和值。然后,它创建一个 Measurement 实例,并将该实例传递到一个 UnitsConverter 对象。清单 6 显示相应的代码。


清单 6. Measurement 和 UnitsConverter
				
case class Measurement(uom:String, amount:Double)

object UnitsConverter{
      // constants
    val lbToKg = 0.45359237D
      val ozToG = 28.3495231
      val fOzToMl = 29.5735296
      val galToL = 3.78541178
      val milesToKm = 1.609344
      val inchToCm = 2.54  
   
      def convert (measure:Measurement)= measure.uom match {
          case "Fahrenheit" => (5.0/9.0)*(measure.amount - 32.0) + " C"
            case "Pounds" => lbToKg*measure.amount + " kg"
            case "Ounces" => ozToG*measure.amount + " g"
            case "Fluid Ounces" => fOzToMl*measure.amount + " mL"
            case "Gallons" => galToL*measure.amount + " L"
            case "Miles" => milesToKm*measure.amount + " km"
            case "Inches" => inchToCm*measure.amount + " cm"
            case "Celsius" => (9.0/5.0*measure.amount + 32.0) + " F"
            case "Kilograms" => measure.amount/lbToKg + " lbs"
            case "Grams" => measure.amount/ozToG + " oz"
            case "Millileters" => measure.amount/fOzToMl + " fl. oz."
            case "Liters" => measure.amount/galToL + " gallons"
            case "Kilometers" => measure.amount/milesToKm + " miles"
            case "Centimeters" => measure.amount/inchToCm + " inches"
            case _ => ""
      }
}

Measurement 是一个 case 类。这是 Scala 中的一个方便的特性。用 “case” 修饰一个类会导致这个类生成这样一个构造函数:这个构造函数需要类的属性,以及 equals、 hashCode 和 toString 的实现。它对于像 Measurement 这样的数据结构类非常适合。它还为定义的属性(在这里就是 uom 和 amount)生成 getter 方法。也可以将那些属性定义为 vars(可变变量),然后也会生成 setter 方法。仅仅一行 Scala 代码可以做这么多事情!


时间:2009-07-20 09:18 来源:developerWorks 中国 作者:Michael Galpin 原文链接

好文,顶一下
(1)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量