《交互式程序设计 第2版》一3.5 捕获简单用户交互行为

简介:

本节书摘来华章计算机《交互式程序设计 第2版》一书中的第3章 ,第3.5节,Joshua Noble 著 毛顺兵 张婷婷 陈宇 沈鑫 任灿江 译更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.5 捕获简单用户交互行为

从头开始说起吧。最基本的用户交互模式有两种:鼠标和键盘。下面介绍Processing是如何处理这两种交互的。要捕获这两种交互,你需要知道鼠标何时移动,鼠标按键何时按下,鼠标何时拖动(即按住按键不放并移动鼠标),是否有键盘按键被按下,如果有的话,被按下的是哪个键。与这些相关的方法和变量是现成的,就存在于Processing应用程序中。你只需要使用它们,也就是说,你只需要告诉Processing系统,当某个方法被调用时你想做什么事情,或者当方法被调用或在draw()方法中时你想访问什么变量。

3.5.1 变量mouseX和mouseY

内置变量mouseX和mouseY是鼠标当前位置坐标,单位是像素。如果鼠标在窗口客户区的左上角,那么mouseX和mouseY都是0。如果鼠标在窗口右下角,并且窗口大小是300像素×300像素,那么mouseX和mouseY都是299。通过这两个内置变量,你任何时候都可以获得鼠标位置。示例3-9中,每次draw()被调用,都输出鼠标位置。
示例3-9:fonts.pde

PFont arial;

void setup() {
    // 设置窗口大小
    size(300, 300);
    // 载入字体
    arial = createFont("Arial", 32);
    // 设置字体,供text()方法在窗口中显示文字时使用
    textFont(arial, 15);
}

void draw() {
    // 背景设置为黑色,将draw()上次显示的文字覆盖
    background(0);
    // 在鼠标当前位置显示字符串,字符串中也包含了鼠标位置
    text(" position is "+mouseX+" "+mouseY, mouseX, mouseY);
}

从代码注释可以看出,该程序不仅使用到鼠标位置,而且用text()方法将鼠标位置作为文本输出到屏幕上鼠标处。这里用到了text()方法,它有3个参数:
void text(string message, xPosition, yPosition);
第一个参数是字符串,第二个和第三个参数是字符串显示位置,它们可以是float型或int型。在前面的例子中,字符串位置被设置为鼠标位置,即变量mouseX和mouseY。

3.5.2 mousePressed()方法

Processing应用程序有一个方法叫mousePressed()。如果应用程序获得焦点,那么每当用户按下鼠标上任何按键,就会执行该回调方法。当然,前面说过,如果没有draw()方法,那么mousePressed()方法是不会执行的。
示例3-10用几个绘图方法演示了如何使用mousePressed()方法。
示例3-10:mpressed.pde

int alphaValue = 0;

void setup() {
    size(350, 300);
    background(0xFFFFFFFF);
}

void draw() {
    background(0xFFFFFFFF);
    fill(255, 0, 0, alphaValue);
    rect(100, 100, 100, 100);
}

void mousePressed() {
    print(mouseX + "\n");
    alphaValue = mouseX;
}

用户按下鼠标的某个键时,mousePressed()方法执行,它将设置填充色的alpha值设置为鼠标x坐标。draw()被不断调用,它用修改后的alpha值构造填充色,画出的矩形的填充色也就发生了改变。

3.5.3 mouseReleased()和mouseDragged()方法

当用户放开鼠标按键,mouseReleased()方法会被执行。和mousePressed()类似,你可以在mouseReleased()方法中放置一些代码,以便在鼠标按键放开时执行。mouseDragged()方法也如此,但它是在用户拖动鼠标(按住鼠标按键并移动鼠标)时被调用的。在鼠标驱动的应用程序中,常常在mouseDragged()方法中将一个布尔型变量设置为真,以表示正在拖动鼠标。示例3-11中,在mouseDragged()方法中放置了绘图代码。
示例3-11:dragged.pde

int lastX = 0;
int lastY = 0;

void setup() {
    size(400, 400);
}
void draw() {
    lastX = mouseX;
    lastY = mouseY;
}
void mouseDragged() {
    line(lastX, lastY, mouseX, mouseY);
}

这是一个简单的绘图程序。用变量存储上一次鼠标的位置,当拖动鼠标时,在上一次位置和鼠标当前位置之间画线。运行这个程序,然后将draw()方法中给lastX和lastY赋值的两条语句注释掉,再运行,看看有什么变化。
即便不要那两条赋值语句,也有别的简单办法来实现自由画线,那就是使用变量pmouseX和pmouseY。它们是程序前一帧的鼠标位置。下面的代码就使用了这两个变量:

void mouseDragged() {
    line(pmouseX, pmouseY, mouseX, mouseY);
}

延伸一下,你现在可以编写这样一个复杂一些的程序:当用户在程序窗口中点击时,以这些点击的位置为顶点画出图形。这个代码示例有几个代码段,你需要一个一个地阅读。第一段是定义一个类。
首先,你要写出单词class和类名(如Point),以及一个左大括号。左大括号表示你即将开始定义方法和数据。

class Point{

然后,定义两个变量来存放点的坐标。

float x;
float y;

下面是该类的构造函数。

Point(float _x, float _y){
    x = _x;
    y = _y;
}

这个构造函数有两个形参_x和_y,它执行时会把这两个传递进来的参数值赋给x和y。一个Point类的具体实例(对象)是点,可以存放用户鼠标点击的位置。
既然点类Point已经定义好,现在可以创建若干个点对象。假设我们最多允许绘制六边形,为了存储6个点,就需要建立一个点数组,它的每个元素都是一个点的实例。

Point[] pts = new Point[6];

还要做的一件事情是编写mousePressed()方法,以便在鼠标按下时,将mouseX和mouseY保存在点数组中。另外,还要在draw()方法中利用这些点绘制多边形。示例3-12中,由于程序较为复杂,故特意将它划分成多个步骤。
示例3-12:point.pde

Point[] pts = new Point[6];

int count = 0;
void setup(){
    size(500, 500);
}

在draw()方法中,用background()方法将背景色设置为白色,用fill()方法将填充色设置为黑色,然后用for循环来绘制那些点构成的多边形:

void draw(){
    background(255);
    fill(0);
    beginShape();
    for(int i = 0; i<pts.length; i++){

一开始的时候,点数组pts中的每个元素都是null。为了避免错误,对于每个元素,都需要确保它是点的实例才能将它纳入到多边形顶点中:

if(pts[i] != null) {

如果它不是null,可以用它来建立一个顶点:

vertex(pts[i].x, pts[i].y);
           }
    }

结束绘图:

endShape();
}

当用户按下鼠标,要将鼠标位置存储到pts数组中备用。具体怎么做呢?可以建立一个新的Point对象,并将当前的mouseX和mouseY传递给它,并将这个对象存储到pts数组中第count个元素那个位置:

void mousePressed(){
    if(count > 5){
       count = 0;
    }
    Point newPoint = new Point(mouseX, mouseY);
    pts[count] = newPoint;
    count++;
}

好了。再写一遍Point类的完整声明:

class Point{
    float x;
    float y;
    Point(float _x, float _y){
           x = _x;
        y = _y;
    }
}

那么,当你在程序窗口中点击时发生了什么?修改Point类的构造函数:

Point(float _x, float _y){
        println(" x is: "+_x+" and y is "+_y);
        x = _x;
        y = _y;
    }

如此一来,每当你在程序窗口中点击时,你将会看到在控制台输出一行信息(当然,具体数据跟你点击的位置有关):

x is: 262.0 and y is 51.0
x is: 234.0 and y is 193.0
x is: 362.0 and y is 274.0
x is: 125.0 and y is 340.0
x is: 17.0 and y is 155.0

那么,这一切是怎么发生的呢?再看看mousePressed()方法:

void mousePressed(){
...
    Point newPoint = new Point(mouseX, mouseY);
    pts[count] = newPoint;
}

每当用户按下鼠标按键时,mousePressed()方法被调用执行。它调用Point类的构造函数,建立了一个点对象,并将mouseX和mouseY传递给这个点。然后将这个点存储到pts数组中,以便draw()方法能使用这些点对象来建立多边形顶点。
前面的例子用到了for循环、类、数组以及vertex()方法。该代码示例有些难度,需要认真思考才能理解。第5章将介绍关于类的更多内容。
3.5.4 变量 keyPressed和key
你或许想知道用户是否按了键盘键。你可以判断用户是否按键,并且可以获知用户按下了哪个键。要实现这个目的,有两种方式。第一种是在draw()方法中检查keyPressed变量:

void draw() {
    if(keyPressed) {
        print(" you pressed "+key);
    }
}

注意,你可以通过key变量来判断用户按下了哪个键。Processing应用程序将每个键都存储在内置变量中。此外,Processing还定义了一个keyPressed()方法,当用户按键时,这个方法会被调用。你可以像使用mousePressed()或mouseMoved()方法一样来使用keyPressed()方法:

void keyPressed(){
    print(" you're pressing a key \n that key is "+key);
}

处理按键的程序代码都应该放到keyPressed()方法中。例如,游戏中用户按下方向键,或者用户在一个文本框中输入名字后按下了回车键。
注意: keyPressed()是一个方法,而keyPressed是一个属性(变量)。它们除了名字相同之外,其他方面有很大区别。只有当用户按键时,keyPressed()方法才会被调用执行;而如果对keyPressed变量的检测是在draw()循环中进行的,你按键的时间是在两次draw()执行的空档,那么程序是检测不到的。
访谈:Ben Fry
Ben Fry和Casey Reas同为Processing项目的发起人。Ben Fry在全球很多地方都做过关于数据可视化、媒体艺术和计算机科学的演讲。他还出版了两本书,一本是《Processing: A Programming Handbook for Visual Designers and Artists》(由他和Casey Reas合著,麻省理工学院出版社),另一本是《Visualizing Data》(O扲eilly出版社)。另外他还创建了大量的数据可视化作品、图表,并撰写了不少论文。
是什么令你对数据可视化产生兴趣的?是出于审美考虑,还是为了解决数据处理的问题,抑或两者皆有?
Ben: 都有。在年少时我就对平面设计和计算机科学产生了兴趣:我在中学时就觉得广告、标志和排印很有趣,而编写我的第一个BASIC程序还要更早。读大学时,我学习平面设计,尤其喜欢资讯设计(和动画以及动态信息显示)。有很长一段时间,我以为我会一直从事UI设计,事实上我的实习和第一份工作都是这个方面的。然而事实并非如此,在麻省理工学院研究生院,我将设计和可视化结合在一起。实际上,在学校里我的学习重点主要是平面设计,以及少量计算机科学(至少本科是这样)。设计是主要科目,因为我认为它会给我带来更多的灵活性以及教我如何去思考。我被领向计算艺术,由于它融合了这两个(当时)完全不同的兴趣点。沮丧了几个月,我意识到我不再是跨学科的了,因为我的两个感兴趣学科融合了。好的一面是,我可以专注于这种直接的融合;而不好的一面是,我不能像年轻时想的那样去单独对待这两个学科了。
我觉得你的工作中有一个中心思想,那就是关于设计师在社会、科学、计算以及其他方面如何定位的问题,在那些方面,视觉设计通常被当做次要因素。这种评价正确吗?在其他那些低估了视觉设计价值的地方,你认为视觉设计还是必需的吗?
Ben: 事实确实如此,凡事有两面。首先,社会、科学、计算这些方面是我最喜欢和好奇的。其次,务实地说,对那些问题感兴趣的人越来越少,所以我能为自己开辟出一个新的方向。
看到你所做的工作,我经常会有“哇哦”这样的反应。起初,视觉的美感和复杂性确实震撼了我,接着我才意识到什么是可视化。这些工作是你有目的地做的,还是你早已听闻,或本身就很想做的?
Ben: 人们倾向于要么让事物更美,要么让它们更有信息含量,很少把它们放在一起考虑。尤其是在处理信息的时候,这种非此即彼的态度更是随处可见。但是除非有东西兼具这两种特性,否则我不太会感觉满意。我希望一个项目在得到一声“喔”的惊叹以后,仍然是有价值的。但是,那些漂亮的部分其实很容易做出来。在某种程度上,我仅仅处理那些我想看的东西,我希望它们不仅有视觉刺激,还有思维上的刺激。当然,这就进入了一个十分主观的领域,而且,我也不愿意把我的作品当做“美丽的东西”——那种意见很个人化,受众有可能同意,也有可能不同意。如果我的工作在人们面前只得到一声“喔”,随后什么也没有,我会认为这是一种失败。如果一幅图没有让人们好奇到想要深入挖掘,没有让人们对其主题产生好奇,那么我就没做好我的工作(至少在我看来是这样)。
一件像“aligning humans and animals”这样的图形块既是一个很好的例证,也是对大量信息的合理、科学、严谨的暗示。这样一来,它让我想起了一些当代艺术家(想到卡斯滕·尼古拉)。他们处理复杂的概念,使它们醒目和易读。您能否谈谈令您感到振奋的一些当代艺术家或设计师?
Ben: 我从排印、运动图形和电影中得到大量灵感。但我难以把它们罗列起来,因为它们总是这里和那里的一些小花絮:库布里克电影艺术,好看的电影资讯图像,马修·卡特的印刷字型中的美和结构的平衡。我办了一个类似博客(http://benfry.com)的网站,这样我就可以把这些东西写下来,并收集在这个地方。当我看到一些东西,比如,科学家浇注熔融铝到蚁洞(http://benfry.com/writing/archives/98),以便看见蚁洞结构(待铝冷却并除去污垢后),我就可以把这个事情发布到博客上。但这是我固执的想法——把下面这些东西联系起来:把蚁群的有机形状与类似的形状联系到一起,如加拿大北部纤细的湖泊形状或复杂的网络拓扑图。
看你在博客上写的文章还有你写的书,你涉及的主题极为广泛——从排印到棒球选手,再到政府和隐私的插图——但这些数据似乎都一团糟。你有没有方法来寻找主题?
Ben: 这在很大程度上只是出于好奇,但这对我来说是组织我的思想的方式。我不喜欢让我的博客、著作等与我的工作直接相关(它们往往会混淆我的想法),但我从来都不知道为什么会这样。在组织思想时,我很容易理解什么是其中最主要的。在一定程度上,这既能提高写作能力,也对健康有益。
至于方法,我努力坚持围绕信息和数据的主题,但偏向电影和运动一些,通常是因为它涉及绘图(与通信有关)或叙述(可视化)。远离政治,或者说,远离上周的“周六夜现场”中的太空奥运会小品,这对我是很难的,但我不想掉进大众喜好的圈子,也不希望我的博客是流水账。
你是否认为你的设计工作可能让你远离信息可视化并通往更具抽象审美的东西,或者你对信息类的东西不感兴趣?
Ben: 不,我想我反而是太沉迷于信息。它可能带我远离设计,并进入我们如何看待数据的那些方面(例如,沿着博客上一串关于隐私和安全的博文,弄清楚“我们拥有这些数据,现在该怎么办?”)。但我同样喜欢视觉方面,所以我认为我不会完全放弃它。
开发Processing,有多少原因是你自己缺乏一个类似的工具,有多少因素是由于想为他人开发工具,这两者你怎么看?
Ben: 嗯,我认为,起初这两个原因同等重要,但后来更多地是让Processing成为大家的工具。为陌生人开发一个工具所花的时间比为自己开发工具要多几个数量级。就算如此,我们也要把公共的部分做完(即便常常对我们自己的工作有影响)。
也就是说,它在很大程度上受我自己的工作的驱动——常常是我自己的开发中用到了一些新东西,然后立即或者很快就去改进Processing。说实话,我们在有些地方(如声音)做得不够好,因为Casey和我在我们自己的工作中都没有充分利用它们。
我认为,Processing更多地是一种“回馈”项目。在我10岁或12岁学习编程时,很多人把他们的程序代码共享给我(无论直接还是间接),我很感激他们。Processing项目也是开放的,有能力的人可以将它往前推进。偶尔我看到用Processing开发出来的作品时,我想过放弃自己开发项目,而更加努力地开发Processing。
作为一个程序员,我真的很喜欢反编译和程序修正,因为这两个不同的过程都围绕着一件事,那就是代码的开发和运行。您怎么看?
Ben: 它们都跟人们脑海中的想象有关……我大致能够想象出代码随时间变化的样子,以及正在运行的代码的样子,但我真的很想看到。我怀疑,得到其他人良好反应的图像未必具有特别的开创性(我们以前看过视觉差异和代码跟踪),这有点像看到一个梦想的实现,或阅读的一篇文章完美地表达了你的看法,比如对一个复杂的政治局势或社会现象的看法。
关于Processing的未来,你有一个清晰的愿景吗?还是规模大又活跃的Processing社区在一定意义上帮助引导该项目?
Ben: 我认为社区已经在很多决策上引导我们了。如果你看看我们在项目刚开始时的目标(在线的Web传输,再加上支持象串行I/O这样的功能、硬件设备等),再看看项目现在的情况(完整的应用程序、图形加速和许许多多的库),你会发现那只“看不见的手”的确在推动。
一些最近的事态发展都与在其他语言(JavaScript、Ruby和Python)中使用API有关,起初这并非真的可行,但我真的很兴奋。现在社区真的把它拿过来用了。
是不是有各种各样的项目,人们并没有真正用Processing深入处理,而你又希望人们那样做?
Ben: Casey和我在这个项目中的个人目标之一是,如果我们让容易的事情变得更容易,让困难的事情不那么难,那么项目的总体质量水平也许能提高一些,或者换句话说,你可以避免很多由于不专业而引发的问题。我认为我们还没有做到我期望的那种成功。
你怎么想到要为非程序员创造一门语言?是Ruby或Python这些“友好”的语言引发的?还是由你想象中的用户要做的事情驱使?或者是发明教学语言的想法促成的?
Ben: 这些因素都有,另外还有我们积累的偏好,以及基于过去经历的严苛意见。
但可以肯定的是,我们还没有创建出一个最终的语言门类,我们只是试图将我们知道的集成在一起,并把它朝我们认为正确的那个方向推进。如果想要有革命性的改变,我认为语法和构思模型就得大改。但我们必须衡量实际(人们接受它离现有语言多远,以及我们创建的软件需要运行多快)。
除了分析数据、理解数据的认知过程,你也可以让用户与它交互工作。你觉得,与数据交互的观念同易读和广博地显示数据的观念是一样丰富的领域吗?
Ben: 哦,那绝对是一个丰富的领域,因为它更多地涉及我们的感官和身体。有一些事情是要盯着看的,我们抓住并操纵、移动它们,看它们做什么(如同看着一个6个月大的婴儿)。 做得越多,我们就能学得越多。

相关文章
事件文本处理流程图
事件文本处理流程图
75 0
事件文本处理流程图