内容


使用 JavaFX 创建 mashup

Comments

开始之前

您将在本文中使用 JavaFX 开发一个富 Internet 应用程序(RIA)。JavaFX 不仅仅是一种用于创建 RIA 的新技术,它还是一种新的编程语言。它从 Java 语言借鉴了很多东西,并且能够在 Java 虚拟机上运行。创建 JavaFX 应用程序并不要求您是专业 Java 程序员,但是熟悉 Java 非常有帮助。您需要用到 JavaFX SDK;本文使用 JavaFX SDK Version 1.0 开发代码。您还需要 Java Development Kit;本文使用 JDK 1.6.0_07 编译和运行代码。可以查看 下载目录 获得下载链接。

JavaFX 模型

我们将通过开发一个典型的 mashup 应用程序来研究 JavaFX。该应用程序将使用来自流行照片共享网站 Flickr 的一个 Web 服务。您将在 Flickr 中搜索照片并将其显示在 JavaFX 应用程序中。首先,将使用 JavaFX 从 Flickr Web 服务中对数据进行建模。

使用 JavaFX 建模

Flickr 提供了可以从任何代码(包括 JavaFX 代码)调用的 RESTful Web 服务。调用 Flickr 的服务需要用到 API 键,这个键可以从 Flickr 免费获得。可以从 Flickr 指定响应格式。默认格式是 XML,因此本文也将使用 XML 格式。清单 1 展示了示例输出。

清单 1. 示例 Flickr XML
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<photos page="1" pages="132147" perpage="6" total="792880">
     <photo id="3196302982" owner="14994333@N08" secret="7c4e209fe9" 
server="3536" farm="4" title="Pond" ispublic="1" isfriend="0" isfamily="0" />
     <photo id="3196288734" owner="19764560@N00" secret="5142dd0a2d" server="3514" 
farm="4" title="Pull In Case Of Fire" ispublic="1" isfriend="0" isfamily="0" />
     <photo id="3195451611" owner="25304693@N00" secret="4ef1d2b63d" server="3528" 
farm="4" title="Domein Nieuwenhoven" ispublic="1" isfriend="0" isfamily="0" />
     <photo id="3196281786" owner="51865294@N00" secret="bed01bff0c" server="3403" 
farm="4" title="Uitdam skyline" ispublic="1" isfriend="0" isfamily="0" />
     <photo id="3196281752" owner="27734999@N03" secret="a81c2e53a5" server="3301" 
farm="4" title="IMG_7642" ispublic="1" isfriend="0" isfamily="0" />
     <photo id="3196281596" owner="51865294@N00" secret="c0f18f3aef" server="3510" 
farm="4" title="Warm scene on a cold day" ispublic="1" isfriend="0" isfamily="0" />
</photos>
</rsp>

可以从清单 1 看到,每张照片都包含几个重要属性:id、secret、server、farm 和 title。前四个属性用于创建图像的 URL。使用 JavaFX 对对象进行建模非常简单,如清单 2 所示。

清单 2. 使用 JavaFX 建模的 Flickr 图像模型
public class FlickrImage{
    var id:String;
    var secret:String;
    var server:String;
    var farm:String;
    var title:String;

    public function getUrl():String{
        "http://farm{farm}.static.flickr.com/{server}/{id}_{secret}.jpg"
    }
}

这个简单的类演示了 JavaFX 语法的一些方面。关键字 var 用于表示 JavaFX 中的一个实例变量。随后将看到,在构建类实例时,类声明中的每个 var 将成为一个命名的参数。注意,JavaFX 是静态输入的,其类型被声明为变量名的后缀。

类还提供一个单一方法 getUrl。方法在 JavaFX 中被声明为函数,并且它们也是静态输入的。JavaFX 有一个可选的关键字 return,它可以根据要求进行工作。清单 2 忽略了这个关键字,因此函数的最后一行将被返回。在本例中为一个字符串表达式。JavaFX 将把实例变量插入到使用花括号表示的字符串的占位符中。注意,这是静态输入的。因此如果在上例中拼错了 “secret”,或尝试调用根本不存在的方法,那么将出现编译时错误。

注意,在 清单 2 中,var 没有任何访问控制修饰符(modifier)。JavaFX 在默认情况下是脚本私有的,因此无法从其他类访问脚本。lone 方法被声明为 public,因此可以供其他类访问。现在您已经拥有一个可以对 Flickr 图像建模的类,因此只需使用某种方法获取数据来填充这些类。为此,需要调用 Flickr Web 服务。

调用 Web 服务

JavaFX 应用程序可以对 Web 进行远程调用。javafx.io.http 包含了一些用于异步调用 Web 服务的类。这些类反过来依赖于 Java 的网络栈。这是 JavaFX 和其他依赖 Web 浏览器进行远程调用的 RIA 技术的显著区别。鉴于此,下面显示的示例应用程序的模型将访问 Flickr 的 Web 服务来获取数据(如清单 3 所示)。

清单 3. 数据访问代码的模型
var pics:FlickrImage[] = [];

public class Model {
    public var tag:String;
    public var onSuccess: function(FlickrImage[]):Void;

    public function searchFlickr(){
        delete pics;
        var url=
            
"http://api.flickr.com/services/rest/?method=flickr.photos.
search&per_page=6&api_key={API_KEY}&tags={tag}";
        var req = HttpRequest{
            location : url
            onInput: function(stream:InputStream){
                parser.input = stream;
                parser.parse();
                onSuccess(images);
            }
        }
        req.enqueue();
    }
    
    def API_KEY = "Put Your API Key Here";
    var images:FlickrImage[] = bind pics;
}

这段代码演示了 JavaFX 语言的更多关键特性。Model 类有两个公共 var。其中一个是称为 tag 的简单字符串。它是用于对 Flickr 进行搜索查询的标记。另一个公共 var onSuccess 是一个函数。函数在 JavaFX 中是一级类。onSuccess 的声明表示它是一个函数,接受 FlickrImage 对象数组作为输入参数并且不返回任何内容(Void)。传递给该参数的任何函数都必须满足这个签名,否则将得到一个编译时错误。

这两个公共 var 都用于公共函数 searchFlickr。在这里使用 HttpRequest 类对 Flickr Web 服务发出调用。注意创建 HttpRequest 的实例的语法。这是 JavaFX 的声明式语法。在本例中,您将两个命名参数传递给 HttpRequest 的构造函数。其中一个是名为 location 的字符串,另一个是名为 onInput 的函数。参数名/值对使用 name : value 表示。因此对于 onInput 参数,将创建一个匿名函数。JavaFX 的静态输入特性允许您知道 onInput 将接受一个函数,该函数接受 java.io.InputStream 作为其惟一输入参数并且具有一个 Void 返回类型。清单 3 中的 onInput 方法将 InputStream 传递给一个解析器对象,随后调用 onSuccess 方法,该方法被传入到 Model 实例。最后,在构建好 HttpRequest 之后,调用其 enqueue 方法。这告诉 JavaFX 运行时在获得空闲线程后立即调用该方法。不要使用主应用程序线程发出远程调用,因为这将使应用程序在等待远程服务器响应时失去响应性。

在研究解析器代码之前,您可能已经注意到 Model 类及其 searchFlickr 方法。它有一个称为 images 的 FlickrImage 对象数组(序列)。这些内容被绑定到称为 pics 的脚本级数组。数据绑定是 JavaFX 的一个重要部分,允许对一个对象进行修改,然后将修改传播给其他对象。在本例中,对 pics 的任何修改都将传播给 images。searchFlickr 方法的第一行使用 JavaFX 关键字 “delete” 清除 pics 序列中的数据。最后,注意 API_KEY 是如何被声明为 def 的。这实际上将其变成一个常量。现在我们已经了解了 Model 类中的所有内容,接下来我们继续解析来自 Flickr 的数据。

JavaFX 中的 XML 解析

在清单 3 中引用了一个解析器,并且将一个 java.io.InputStream 传递给该解析器。在 JavaFX 中使用来自 Java 的核心类非常普遍。在本例中,将 InputStream 传递给 javafx.data.pull.PullParser。这类似于 Java 中的 StAX 解析器。它允许您从解析器接收事件流(类似于 SAX 风格的解析),但是能够通过选择所需的事件来控制事件流。JavaFX 的 PullParser 可以同时恰当地处理 XML 和 JSON 数据 — 这是用于 Web 服务的最常见格式。这使得 JavaFX 非常适合用于创建 mashup,比如本文创建的 mashup。清单 4 展示了解析来自 Flickr 的 XML 的代码。

清单 4. 在 JavaFX 中解析 XML
def ID = QName {
    name : "id"
}
def SECRET = QName{
    name : "secret"
}
def SERVER = QName{
    name : "server"
}
def FARM = QName{
    name : "farm"
}
def TITLE = QName{
    name : "title"
}

def parser = PullParser{
    documentType : PullParser.XML
    onEvent : function(event:Event){
        if (event.type == PullParser.START_ELEMENT){
            if (event.qname.name.equals("photo")){
                var image = FlickrImage{
                    id : event.getAttributeValue(ID)
                    secret : event.getAttributeValue(SECRET)
                    server : event.getAttributeValue(SERVER)
                    farm : event.getAttributeValue(FARM)
                    title : event.getAttributeValue(TITLE)
                }
                insert image into pics;
            }
        }
    }
}

在清单 4 中,首先要注意的是我们定义了一些 QName 对象。它们在 XML 术语中被称为限定名,表示来自 Flickr 的 XML 属性限定名。如您所料,JavaFX 的 QName 类允许使用名称空间、前缀和名称。Flickr XML(清单 1)没有使用名称空间,因此只需要对清单 4 中的 QNames 使用一个简单名称。

PullParser 实例是使用 JavaFX 的声明语法创建的。您应该很熟悉!在清单 4 中,只需设置 documentType 属性表示正在解析 XML。然后将一个匿名函数(closure)传入到 onEvent 属性。然后查找 START_ELEMENT 事件,如果元素为 “photo”,就从其属性提取数据并使用数据创建一个新的 FlickrImage 实例。然后需要将该实例添加到 pics 序列。注意如何通过 JavaFX 保留字 insert 实现这一过程。此实例将始终添加到序列的末尾。最后要注意,所有 QNamesPullParser 都被定义为 def(也就是常量)。您可以将它们设为 var,但是设为 def 更好。这类似于在 Java 中将其声明为 final。

现在我们已经了解所有数据处理代码,知道如何调用一个远程 Web 服务以及如何解析该服务返回的 XML。接下来我们看一看更有魅力的用户界面代码,JavaFX 是凭借这些代码才出名的。

JavaFX 的用户界面代码

当 JavaFX 在 2007 年首次引入 JavaOne 时,它被当作一种可以创建丰富用户体验的技术。JavaFX 中的 UI 一直关注的焦点,因此 JavaFX 在这方面的表现十分出众。让我们看一下 mashup 的用户界面。首先看看扩展 JavaFX 中的一些核心类有多么简单。

扩展 JavaFX

应用程序需要显示来自 Flickr 的图像,因此需要使用 JavaFX 的 ImageImageView 类。工作流程非常简单。使用 FlickrImage 对象计算图像的 URL,并使用该 URL 创建一个 Image 实例,然后再使用这个 Image 实例创建一个 ImageView 对象。随后可将 ImageView 对象添加到 Scene(稍后详细介绍)。让我们使用清单 5 所示的新类封装这种逻辑。

清单 5. FlickrView
public class FlickrView extends ImageView{
    public var flickrImage:FlickrImage;
    init {
        this.image = Image { 
            url : flickrImage.getUrl()
        }
    }
}

可以看到,FlickrView 类是 ImageView 的子类。它添加了一个新的 var flickrImageinit 方法类似于 Java 中的构造函数,调用方设置完所有 var 后将立即调用该方法。在本例中,它使用 FlickrImage 实例创建一个 Image 实例(其 URL 由 FlickrImage 计算得出)。在 JavaFX 中创建子类非常简单;就像使用 Java 创建子类一样。构造函数也可以方便地使用 init 块。使用 FlickrView 可以方便地显示 Flickr 中的图像,因此您现在可以准备创建用户界面的其余部分。

创建用户界面

到目前为止,您已经了解如何在不同位置使用 JavaFX 声明式语法构造新的对象。这对于开发人员非常方便。如果您从事过大量 JavaScript 编程,应该会非常熟悉这种编程风格。这种编程风格在创建用户界面时才真正表现出自己的魅力。应用程序的主要用户界面如清单 6 所示。

清单 6. 用户界面代码
Stage {
    title: "FlickrFX"
    width: 500
    height: 500
    scene: Scene {
        fill: LinearGradient {
            startX: 0.0, startY: 0.0, endX: 0.0, endY: 1.0
            proportional: true
            stops: [ Stop { offset: 0.0 color: Color.ORANGE },
                     Stop { offset: 1.0 color: Color.BLUE } ]
        }
        content: VBox{
            spacing : 50
            content : [
                HBox {
                    spacing : 20
                    content : bind topRow
                },
                HBox{
                    spacing : 10
                    content : [
                        Text {
                            font: Font {
                                size: 24
                            }
                            x: 10,
                            y: 30
                            content: "Tag"
                        },
                        input,
                        SwingButton {
                            text: "Search"
                            action: function() {
                                var model = Model {
                                   tag : input.text
                                   onSuccess : displayImages
                                }
                                model.searchFlickr();
                            }
                        }
                    ]

                },
                HBox {
                    spacing : 20
                    content : bind bottomRow
                }
            ]
        }
    }
}

清单 6 的代码乍看起来有些复杂,实际上非常简单。让我们先从代码顶部开始研究。本例中,Stage 是应用程序的主要显示窗口。代码构造了一个新的 javafx.stage.Stage 实例。它将 Stage 实例的 title 变量设置为 FlickrFX,并将它的 height 和 width 变量设置为 500。这时的应用程序应该如图 1 所示。

图 1. JavaFX 应用程序的骨架
JavaFX 应用程序的骨架
JavaFX 应用程序的骨架

这个应用程序并不是十分有趣,但是可以从中受到启发。接下来代码将要设置 Stage 实例的 scene 变量。它将这个变量设置为 javafx.scene.Scene 类的一个新实例。Scene 执行的第一个操作是创建一个背景填充。在本例中它将 Scene 实例的 fill 变量的值设置为 javafx.scene.paint.LinearGradient 的新实例,从而创建一个渐变效果填充。这种渐变效果从顶部的橙色开始,逐渐渐变为底部的蓝色。LinearGradient 的 startX、startY、endX 和 endY 决定渐变的起始点和终止点。这种渐变呈垂直型,因为 startX 和 endX 是相同的,但是 startY 和 endY 将从 0.0 变为 1.0。最终,LinearGradient 的 stops 变量被设为一组 javafx.scene.paint.Stop 对象。通过这种方式就可以设置橙色和蓝色的起始点。添加了这些渐变代码后,应用程序将如图 2 所示。

图 2. 添加渐变效果的骨架应用程序
添加渐变效果的骨架应用程序
添加渐变效果的骨架应用程序

应用程序现在已经添加了一些颜色!因此您只需设置 Scene 实例的 content 变量。将其设置为 javafx.scene.Node 对象。Node 是一个抽象类,是 JavaFX 中所有图形对象的基类。在本例中您构建了一个 javafx.scene.layout.VBox 实例。VBox 是一个容器,它将以从上到下的垂直方式铺设内容。我们将它的 spacing 变量设置为 50,因此它将在所包含的每个对象之间放置 50 个像素。

将对象添加到 VBox 很简单:把它们放入其值为 VBox 的 content 变量的 Node 序列中。在本例中您构造了三个 javafx.scene.layout.HBox 实例。从名称可以看出,HBox 类似于 VBox,但它是从左到右水平放置内容的。这样,通过在一个 VBox 内堆叠三个 HBox 实例,就得到一个灵活的网格。

顶部和底部的 HBox 实例被绑定到另外两个脚本变量 topRowbottomRow。您再一次使用了 JavaFX 强大的绑定构造。当 topRowbottomRow 被修改时,HBox 实例也会相应地改变,因此用户界面的各个部分将执行刷新。那么 topRow 和 bottomRow 的值是什么呢?代码如清单 7 所示。

清单 7. 网格的首行和末行
def LOGO = Image {
    url: "http://lh3.ggpht.com/_XmwdENwf53s/SW06N4aWEDI/AAAAAAAAAgo/
nXzkz7IBEnk/javafx.png"
}

var topRow = for (i in [1..3]) {
    ImageView{
        image: LOGO
        fitWidth : 150
        fitHeight : 150
        preserveRatio: true
        smooth: true
        cache: true
    }
}
var bottomRow = for (i in [1..3]) {
    ImageView{
        image: LOGO
        fitWidth : 150
        fitHeight : 150
        preserveRatio: true
        smooth: true
        cache: true
    }
}

我们在这里使用了 JavaFX 的另一个有用特性。topRowbottomRow 变量通过一个循环表达式构建(通常为列表包含)。这是一个出色的 JavaFX 特性。等效的 Java 代码不仅更加冗长,而且在执行时不是很明确。在本例中,JavaFX 的语法使其在创建 ImageView 实例序列时非常明确,其中每个 ImageView 实例使用称为 LOGO 的常量 Image 实例。

了解顶部行和底部行之后,那么什么是中间行呢?从 清单 6 可以看到,它创建了一个值为 “Tags” 的标签(javafx.scene.text.Text 的实例)和一个按钮。通常情况下,按钮为 javafx.ext.swing.SwingButton 的实例。JavaFX 利用了很多 Swing 对象,但是得益于其方便的语法,这些对象更加易于使用。按钮显示为 “Search”,它的 action 变量被设置为一个闭包。我们将在下一小节深入探讨它。最后,中间行还引用了另一个脚本变量 input。由于是显式创建的,因此可以在代码的其他部分中引用它(即在按钮的 action 闭包中)。为了保持完整性,其代码显示在清单 8 中。

清单 8. input 控件
def input = SwingTextField {
    columns: 10
    editable: true
}

类似于按钮,这也是一个基于 Swing 的控件。在本例中是 javafx.ext.swing.SwingTextField 的实例。注意这里再次使用了 def,因此不能被修改。现在已经准备好所有控件,让我们看一看应用程序是什么样的,如图 3 所示。

图 3. 初步完成的 UI
初步完成的 UI
初步完成的 UI

现在应用程序具备了所有 UI 元素。接下来将把视图与前面创建的模型关联起来。您需要使用一些控制器代码将所有内容组合起来,并使这个 RIA 具有交互性。

将所有内容关联起来:JavaFX 控件

回到 清单 6,我们使用了一个按钮,它的 action 变量被设置为一个内联函数。现在我们仔细研究一下。该函数创建了一个 Model 实例(清单 3),它传递输入对象的 text 变量的值和对另一个函数 displayImages 的引用。随后调用 Model 实例的 searchFlickr 方法。从 清单 3 中我们了解到,当对 Flickr 的远程调用完成并且结果被解析到 FlickrImage 对象之后,将调用 displayImages 函数。看一下清单 9 中的 displayImages 函数。

清单 9. 显示来自 Flickr 的图像
function displayImages(images:FlickrImage[]){
    delete topRow;
    delete bottomRow;
    for (i in [1..3]){
        insert FlickrView {
            flickrImage : images[i-1]
            fitWidth : 150
            fitHeight : 150
            preserveRatio : true
            smooth : true
            rotate : 60*(i-2)
        } into topRow
    }
    for (i in [1..3]){
        insert FlickrView {
            flickrImage : images[i+2]
            fitWidth : 150
            fitHeight : 150
            preserveRatio : true
            smooth : true
            rotate : -60*(i-2)
        } into bottomRow
    }
}

JavaFX 使您能够编写非常简洁的代码。这个函数删除 topRowbottomRow,并清除该序列中的所有 Node。然后使用 JavaFX 的 for 循环遍历图像对象。对于每个 FlickrImage 实例,将创建一个 FlickrView 实例并使用 JavaFX 的 insert 语句将其添加到 topRowbottomRow。您还注意到,每个 FlickrView 实例都设置了自己的 rotate 变量。这是来自 Node 的变量(因此任何图像对象都可以旋转)。它执行一定角度的旋转以应用到 Node。这使您可以对每个图像进行巧妙的处理,如图 4 所示。

图 4. 显示来自 Flickr 的图像
显示来自 Flickr 的图像
显示来自 Flickr 的图像

注意,搜索结果会随时间而变化。回过头看看 清单 8,您将发现每个图像都有自己的 fitWidthfitHeight 变量,并且被设置为 150,而 preserveRatio 变量则被设置为 true。可以看到图 4 所示的结果。JavaFX 运行时动态调整图像的大小。图像的高度和宽度的最大值被设置为 150 像素,但是保持高宽比例不变,因此图像没有变形。

结束语

在本文中,您使用 JavaFX 构建了一个 mashup 应用程序。它展示了如何调用远程 Web 服务以及如何解析远程服务提供的数据。您了解了如何使用 JavaFX 创建用户界面,以及如何将控制器逻辑与用户界面元素关联起来。这些都是创建 mashup 过程中最常见的任务。此外,您还理解 JavaFX 如何简化这些任务。这个过程提供了一些演示 JavaFX 语法的重要特性的例子:类型系统、闭包支持、序列语法、强大的数据绑定,以及用于构造对象的声明式语法。现在,您应该能够使用 JavaFX 构建自己的 mashup 应用程序了。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=382829
ArticleTitle=使用 JavaFX 创建 mashup
publish-date=04162009