安卓小部件设计
多年来,Web设计人员已悄悄地在流行的Web浏览器中利用JavaScript解释器来增强其网站的外观。 他们主要是通过将简短的代码片段复制到HTML页面中来实现的。 现在,随着Ajax的近来流行,软件工程师还使用JavaScript来开发在浏览器中执行的新一代应用程序。 随着基于浏览器的应用程序规模的增长,它们将越来越需要与其他执行环境相同的设计模式和开发准则。
基于浏览器的应用程序在实时环境中执行,鼠标,键盘,计时器,网络和程序事件可随时发生。 当事件驱动的应用程序的行为取决于事件发生的顺序时,其编程可能会变得很纠结,因此难以调试和修改。 软件工程师长期以来使用有限状态机 (在学术界有时称为离散或确定性有限自动机)作为开发事件驱动程序的组织原理。
有限状态机施加的约束通过用简单的表替换缠结的逻辑为设计增加了严谨性,从而实现了更简单的实现和更轻松的测试。 传统上,有限状态机已被证明有助于开发各种程序,例如网络驱动程序和编译器。 它们将同样有助于开发基于浏览器的应用程序。
在本系列中,您将开发一个示例有限状态机应用程序作为练习,以探索JavaScript语言的一些独特功能:
- 函数是一流的对象:就像其他任何对象一样,它们可以被创建,分配给变量并作为参数传递。 可以在另一个函数中定义函数并将其分配给全局变量或作为结果返回。 这些函数将在定义它们的函数返回后继续存在。
- 函数可以引用其词法范围内的任何变量(函数定义周围的嵌套花括号),例如定义它们的函数的局部变量。 这些变量成为函数闭包的一部分(函数,其自身的变量以及它使用的在其词法范围内其他位置定义的任何变量),并且在返回定义它们的函数之后它们也将保留。
- 函数可以存储在关联数组 (通过名称而不是数字索引的数组)中。
这些语言功能提供了一种简洁明了的方式来组织事件和状态之间转换的动作,以及一种优雅的方式来应对浏览器事件模型之间的差异。
与大多数浏览器内置的默认工具提示相比,示例应用程序FadingTooltip更为详尽。 用FadingTooltip小部件创建的工具提示使用动画淡入和淡出视图,而不是弹出并突然消失,它们随光标一起移动。 用于设计此行为的有限状态机模式使其逻辑透明。 用于实现它JavaScript语言功能使源代码紧凑高效。
本文介绍如何使用有限状态机的图形和表格表示来设计动画小部件的行为。 随后的文章将描述如何在JavaScript中实现有限状态机的表表示,以及如何处理在流行的浏览器中测试实现的实际问题。
基本工具提示
当光标悬停在某些视觉控件(例如按钮,选择器或输入字段)上时,大多数现代的图形应用程序可以临时显示包含有用的定义,说明或建议的小文本框。 这些有用的文本框在早期的Apple系统中称为“气球帮助”。 在某些IBM产品中,它们被称为infopops,在某些Microsoft产品中,它们被称为ScreenTips。 在本文中,我使用了更通用的术语tooltip 。
流行的Web浏览器(例如Netscape Navigator,Microsoft Internet Explorer,Opera和Mozilla Firefox)将显示具有title
属性的任何HTML元素的工具提示。 例如, 清单1显示了三个具有title
属性HTML元素。
清单1.浏览器工具提示HTML代码
Here are some <span title='Move your
cursor a bit to the right, please.'> fields with built-in tooltips </span>: <input
type='text' title='Type your bank account and PIN numbers here, please ...' size=25>
<input type='button' title='Go ahead. Press it. What's the harm? Trust me.' value='Press this
button'>
示例页面显示了浏览器如何使用title
属性呈现HTML元素。 请注意,当您将光标移到元素上时,工具提示的显示和消失方式。 文本框包含简单的文本,没有任何格式或样式。 它们在光标移动一小段时间后便会弹出视图,并在经过任意时间后突然消失,或者光标移离HTML元素,或者按下了一个键。 浏览器一次不会显示多个文本框。 这些工具提示的外观和行为都硬连接到浏览器中。 您无法更改它们。
更详尽的工具提示
内置工具提示有很大的改进空间,最新版本的流行浏览器提供了构建更详尽工具提示所需的所有原始内容。 HTML Division元素创建一个框,您可以将其放置在浏览器窗口中的任何位置。 您可以使用级联样式表(CSS)确定框外观的几乎每个方面。 用JavaScript编程的光标移动可以触发浏览器窗口中任何视觉元素的特定动作。 您可以对计时器进行编程以对这些动作进行排序。
示例页面还包含一些HTML元素,这些元素带有更详尽的工具提示。 如果您运行的是流行浏览器的最新版本,则可以将更详细的工具提示与内置的工具提示进行比较:
- 它们会淡入视图,然后从视图中淡出,而不是突然弹出和弹出。
- 它们既包含图像又包含文本,并经过格式化和样式设置。
- 当它们可见时,它们随光标移动。
- 当光标移离HTML元素然后返回到HTML元素时,淡入淡出将反转方向。
- 可以同时看到多个工具提示,有些提示会逐渐消失,而另一种则会逐渐消失。
这种增强的行为和外观不仅是外观。 它提高了可用性。 面对包含数十或数百个元素的繁忙页面的用户可能会错过一个即时弹出的工具提示。 对运动特别敏感的人类视觉系统更容易注意到工具提示,该工具提示会淡入视图并随光标移动,即使分散注意力的用户的注意力集中在页面的其他位置。 与未格式化的文本相比,图像,格式和样式可以更有效地传达信息。 而且,这些更详细的工具提示的所有参数都是可配置的。
本文的其余部分着重于将FadingTooltip小部件设计为有限状态机。 随后的文章将展示如何实现和测试代码。 但是,如果您很着急, 参考资料链接到JavaScript源以及使用它HTML网页,以产生上述示例。
有限状态机
有限状态机对行为进行建模,其中对未来事件的响应取决于先前的事件。 在这一领域有很多学术文献(请参阅参考资料 ),但是有用的工作定义很简单。 有限状态机是包含以下内容的计算机程序:
- 程序响应的事件
- 在程序事件之间等待美国
- 响应事件而在状态之间进行转换
- 过渡期间采取的行动
- 变量持有事件之间的动作所需的值
有限状态机在行为由许多不同类型的事件驱动,并且对特定事件的响应取决于先前事件的顺序的情况下最有用。 驱动有限状态机的事件可以在计算机外部发生,起源于键盘,鼠标,计时器或网络活动,也可以在计算机内部发生,起源于应用程序的其他部分或其他应用程序。
状态是记住以前事件的一种方式,过渡是组织对未来事件的响应的方式。 必须将状态之一指定为初始状态。 可能有一种最终状态,但这是可选状态,并且FadingTooltip小部件没有状态。
有限状态机的两种常见表示是:
-
有向图
- 气球代表状态,它们之间的箭头代表过渡,上面标有事件和动作。 二维表
- 行和列表示事件和状态,单元格包含操作和转换。
这些表示是等效的,但强调设计的不同方面。 两者都很有用,在本文后面的部分中我将同时使用它们。
用有限状态机开发事件驱动程序比普通过程编程要复杂一些。 通常,它需要更多的纪律,尤其是更多的设计工作。 如果做得好,它可以简化代码,加快测试速度并简化维护。 即使这样,对于所有事件驱动程序来说,有限状态机的复杂性也不值得。 例如,当事件的种类很少时,或者事件触发的动作始终相同时,可能就没有理由进行额外的开发工作。
有限状态机和运行时环境
有限状态机是事件驱动的,并且需要一种使其自身在运行时环境中与感兴趣的事件挂钩的方法。 这些钩子称为事件处理程序 ,是插入运行时环境中的非常小的代码片段,因此它们将在发生特定事件时执行。 执行时,事件处理程序需要获取一些基本信息:
- 发生的事件的类型(例如,光标已移动或计时器已过期)
- 事件的上下文(例如,光标位于哪个HTML元素上,或者哪个网络请求已完成)
- 有限状态机自身变量和方法的位置
JavaScript非常适合构建事件驱动的有限状态机。 确实,JavaScript可能太适合了-它具有三种不同的挂钩事件的方法。 这些事件模型中的每一个都是简单明了的,但是程序必须实现这三个事件模型 ,以确保它们将在所有流行的浏览器上运行。 事件的上下文直接传递给其中两个事件模型中的事件处理程序。 对于另一个,JavaScript函数闭包允许将事件的上下文与其事件处理程序一起包含。
JavaScript提供了一个对象模型 ,该对象模型对于Java和C ++程序员而言似乎有些特殊,但是对于编码有限状态机的变量和方法而言,它是完全足够的。 而且,JavaScript关联数组允许您直接对有限状态机的二维表表示进行编码。
有条不紊地设计行为
有限状态机的基本要素是它响应的事件以及事件之间等待的状态。 设计必须针对每种可能的状态考虑每种可能的事件:
- 事件是否可能在该状态下发生
- 应该采取什么行动来处理事件
- 事件后转换为哪个状态
- 事件之间需要记住哪些变量
我以图1所示的图形开始设计过程,该图将状态显示为气球,并将过渡显示为连接气球的箭头。 我以一张表结束, 如图4所示,该表分别将事件和状态列为行和列标题。 表格中的每个单元格都列出了在特定状态下发生特定事件时要执行的操作,或者指示该事件不能在该状态下发生。
您通常需要此设计过程的几次迭代才能正确显示图形和表格。 对于具有许多事件和状态的有限状态机,该过程可能非常繁琐,并且需要一定的纪律来在每次迭代期间有条理地遍历表中的每个单元。 这迫使您考虑在每种可能的情况下想要什么样的行为。 您可能发现了进一步阐述或完善行为的机会,或者发现您所需要的状态比最初想像的更多(或更少),或者您可能不得不在单元格之间调整操作以在每种情况下指定正确的行为。
这种设计有限状态机的有条理的过程虽然很繁琐,但却是值得的。 完成的表如图4所示,显示了行为的所有逻辑,并且可以直接转换为代码(请参见actionTransitionFunctions源代码)。
关于JavaScript功能
要设计FadingTooltip小部件,您需要了解一些JavaScript的功能。 本着自顶向下设计的精神,我将在此处草绘基本思想,并将实现的详细信息推迟到本系列的下一篇文章中。
当光标经过页面上的任何HTML元素时,所有流行的浏览器都可以将事件传递给JavaScript代码。 这些事件称为mouseover , mousemove和mouseout ,指示光标已移到该元素上,在其上四处移动并且已从HTML元素移开。 浏览器通过这些事件传递当前光标位置。 事件发生时,可以对JavaScript进行编程,以动态创建HTML Division元素,用文本,图像和标记填充它们,并将它们放置在光标附近。
浏览器没有本机的淡入或淡出功能,但是可以通过随时间变化Division元素的透明度(实际上是不透明度,与透明度相反)来模拟此效果。
JavaScript有两种类型的计时器:单次使用的计时器在到期时会生成超时事件,而重复的报价器会定期生成计时事件。 您需要FadingTooltip小部件。
绘制状态图
通过查看FadingTooltip小部件所需的基本行为来开始设计。 当光标经过特定HTML元素时,您希望窗口小部件等待光标在该元素上暂停。 如果是这样,则您希望该小部件将工具提示淡入视图,显示一会儿,然后使其淡出。
您的有限状态机将需要响应以下事件:
- 当光标经过特定HTML元素,在其中移动并最终离开该元素时,浏览器可以将mouseover,mousemove和mouseout事件传递给JavaScript。
- JavaScript可以对超时事件进行编程,以指示光标何时停顿了足够长的时间或工具提示已显示足够长的时间,而timetick事件可以对工具提示的不透明度进行动画处理,以分别模拟淡入或淡出。
您将发明一些状态,以使计算机在事件之间等待。 让我们将小部件的初始状态称为“非活动”,这是等待鼠标悬停事件激活的状态。 窗口小部件将在“暂停”状态下等待,直到超时事件指示光标在HTML元素上暂停足够长的时间为止。 然后,小部件将在FadeIn状态下等待,同时使用timetick事件为淡入淡出动画,然后在Display状态下等待另一个超时事件。 最终,小部件将在FadeOut状态下等待,同时为淡出动画设置更多的时间刻度事件。 小部件将返回“非活动”状态,在此状态下它将等待另一个鼠标悬停事件。
图1将此进展绘制为图形,将状态表示为气球,将过渡表示为连接它们的箭头,并将事件表示为箭头上的标签。 双边框表示初始状态的气球。
图1.状态图的初始草图
FadingTooltip小部件必须针对其处理的每个事件采取一些措施:
- 当鼠标悬停事件在非活动状态下发生时,必须在暂停状态下等待之前启动一次计时器。
- 当发生超时事件时,它必须创建工具提示(初始不透明度为零)并开始重复的代码,然后再以FadeIn状态等待。
- 每次发生时间刻度事件时,都必须稍微增加工具提示的不透明度。 达到工具提示的最大不透明度时,它必须取消置顶指示器并启动另一个计时器,然后再等待处于“显示”状态。
- 当发生超时事件时,它将必须启动另一个代码,然后才能处于FadeOut状态。
- 每次在FadeOut状态下发生计时事件时,都必须稍微降低工具提示的不透明度。 当工具提示的不透明度达到零时,小部件将取消该代码,删除该工具提示,然后返回到非活动状态,在该状态中它将等待再次被另一个鼠标悬停事件激活。
图2通过将这些动作列出在触发它们的事件下方,从而在草图中记录了这些动作。
图2.状态图的初始草图,将动作附加到事件
将状态图转换为状态表
上面显示的图形表示法是开始设计有限状态机的好方法。 但是表表示形式更适合完成设计,因为它公开了要考虑的所有事件和状态组合。
要将状态图转换为状态表,请使用事件名称标记行,并使用状态名称标记列。 名称的顺序是任意的; 我将初始状态放在第一列中,并将启动事件放在第一行中。 然后,将每个事件的操作和下一个状态复制到表的相应单元格中, 如图3所示。
图3.对应于初始状态图的初始状态表
完成状态表
要完成有限状态机的设计,请考虑表中的每个空单元格。 您需要针对每个单元格考虑该事件是否可以在该状态下发生,如果是,则在这种情况下您的小部件应采取的动作以及下一个状态应该是什么。 这是设计过程中乏味但非常必要的部分。
您认为单元格的顺序无关紧要。 通常在设计过程中多次重复执行此步骤,反复考虑每个单元,频繁地修改其内容,并且每次以不同的顺序进行。 随着设计的发展,添加(或删除)状态也很常见,这会促使进一步的修订。 我将跳过这些迭代,并依次检查每个状态和事件的结果来总结此设计步骤。
-
无效状态
-
在此状态下,仅会发生您已经考虑的启动事件,因为mousemove和mouseout事件之前应有mouseover事件,并且不运行任何计时器。
因此,您可以将第一列中的所有其他单元格标记为“不应发生”。
但是,在继续之前,请考虑将鼠标悬停事件用于此状态。 最终为工具提示创建HTML Division元素时,需要将其放置在光标附近,因此要保存当前光标位置,浏览器将通过此事件传递该位置。 并且,优良作法是在启动新计时器之前取消任何正在运行的计时器。 将这些操作添加到鼠标悬停单元格。
暂停状态
-
在等待其计时器到期时,光标可能会在HTML元素内移动或从HTML元素移开。 确定如果发生这些事件应采取的措施,以及下一个状态应为什么。 如果在此状态下发生mouseout事件,则希望FadingTooltip小部件返回到“非活动”状态,就好像光标根本没有越过HTML元素一样,但是您确实需要取消计时器。 在mouseout单元中记录此动作和过渡。
另一方面,对于mousemove事件,您希望小部件继续等待光标暂停,这要求您取消并重新启动计时器。 因为您希望工具提示显示在光标附近,所以您想更新保存的光标位置。 通过查看,您可能会意识到暂停状态下的mousemove事件的操作和转换与非活动状态下的mouseover事件的操作和转换相同。 不要在两个单元格中重复所有操作,而是直接在mousemove单元格中指示此重复。 将该列中的所有其他单元格标记为“不应发生”。
渐入状态
- 在此状态下,在模拟带有时间标记事件的淡入时,光标可以继续移动。 如果发生mousemove事件,请移动工具提示使其匹配以保持当前状态。 如果发生mouseout事件,请在自动收录器仍处于运行状态的情况下过渡到FadeOut状态,以便后续的timetick事件将从其当前值减小工具提示的不透明度。 在适当的单元格中记录这些事件和过渡,并在此列中将所有其他单元格标记为“不应发生”。 显示状态
- 当然,光标可以继续移动。 如果它在HTML元素内移动,您将希望执行与FadeIn状态相同的操作-移动工具提示以使其匹配。 如果它离开HTML元素,请执行与“显示”状态下的超时事件相同的操作和过渡。 在mousemove和mouseout单元格中直接指示这两个重复项,并将所有其他单元格标记为“不应发生”。 淡出状态
-
在此状态下,在模拟带有时间刻度事件的淡出时,光标仍然可以移动。
如果它在HTML元素内移动,请执行与FadeIn和Display状态相同的操作。
如果鼠标从HTML元素移开,则无需执行任何操作-代码将继续运行,以便后续的时间标记事件将使工具提示的不透明度从其当前值降低到达到零。
不要将此单元格标记为“不应发生”,但确实表示不需要采取任何措施。 并且,如果光标移回HTML元素上方,则将工具提示移回光标并返回FadeIn状态。
图4显示了所有这些其他动作和过渡。 其余的空单元格是“不应发生”的情况。
图4. FadingTooltip小部件的状态表,按设计
有限状态机的表表示形式始终可以转换回图形,因为这些表示形式是等效的。 图5显示了完成表的图形表示。
图5.设计的FadingTooltip小部件的状态图
收集状态变量列表
状态表和图形完成后,再回顾一次以收集机器在事件之间需要记住的变量列表是很有用的,这样它就可以在不同的单元格中执行相关的操作。 您的有限状态机将需要清单2中的状态变量。
清单2.状态变量的初始列表
currentState string value equal to one of the
state names currentTimer pointer to timer object, obtained when set, used to cancel
currentTicker pointer to ticker object, obtained when started, used to cancel currentOpacity
float that varies from 0.0 (invisible) to 1.0 (fully visible) lastCursorPosition floats obtained
from cursor events, used when an HTML Division element is created tooltipDivision pointer to
HTML Division element, set when created, used when faded, moved, or deleted
尽管JavaScript变量是无类型的,但它们包含的值是有类型的(即,可以将任何类型的值分配给任何变量)。 本着这种精神,我为状态变量选择了名称,并在注释中指出了我希望分配给它们的类型。
准备实施
有了完成的状态表和状态变量列表,您就可以实现有限状态机了。 请继续关注,因为这是下一篇文章的重点。 但是,请记住,开发是一个反复的过程。 您可能需要返回设计阶段...
翻译自: https://www.ibm.com/developerworks/web/library/wa-finitemach1/index.html
安卓小部件设计