安安画画行为编辑
实现小球随机运动
首先实现小球在树枝周围的随机运动。这些小球的运动轨迹是跳跃式的,每隔0.5秒钟就在原地消失,出现在另外一个地点,这个地点是随机的。为了实现这个效果,有两个主要问题要解决:
(1)如何控制小球出现的位置;
(2)如何让改变小球位置的行为规律性地定时出现?
了解绘图和动画
在App Inventor 里面,针对绘图和动画应用,专门提供了3个组件:画布、图像精灵和球形精灵。画布是绘图和动画的基础,图像精灵和球形精灵只能放置在画布上才能工作。在画布里可以设置绘画的画笔颜色、线宽等属性,还提供多种绘画的方法(过程)供开发者调用,比如画圆、画线、画点、画字、保存等;另外还提供了多种事件的响应入口,比如被触碰、被划动、被拖动等,开发者可以在这些事情处理器中拼入响应模块,实现App的行为。
小球在画布中出现的位置是由其坐标决定的。画布的坐标系和直角坐标系不同,如图4.4(a)所示,画布坐标系的原点(0,0)在左上角,X坐标轴往右递增,Y坐标轴往下递增。小球的X坐标取值为其最左端离画布左边距的像素点,Y坐标为其最上方离画布上边距的像素点。如果小球的坐标为(0,0),则在画布中的位置如图4.4(b)所示。 小球的平面坐标值(x,y)决定了小球在画布上的平面位置。如果希望小球出现在画布上任意位置时都能保持完整,那么要注意小球不能有部分超出画布边界。这可以通过限制小球的坐标取值范围来实现。只要小球的X坐标的取值区间为[0,画布宽度-小球直径],Y坐标的取值区间为[0,画布高度-小球直径]就能达到要求。
让小球移动到随机位置
球形精灵粉色、球形精灵红色和球形精灵黄色1都是在画布上随机移动的,这只需要在它们的坐标取值区间随机产生坐标值即可。如改变球形精灵粉色位置的代码模块如图4.5所示。 让两个黄色小球保持相对位置固定
本例中球形精灵黄色2和球形精灵黄色1的位置相对固定,以后以这两个小球所在位置作为两个端点画的直线也是平行的。因此球形精灵黄色2不能再设置随机的坐标值,而是要根据球形精灵黄色1的坐标来设定。
假设球形精灵黄色1的坐标为(x1,y1),那么球形精灵黄色2的坐标将被设置为(x1-12,y1-24),这样一来两个黄色小球的位置就相对固定了。
为了保证球形精灵黄色1和球形精灵黄色2都不超出画布,因此把球形精灵黄色1的X坐标取值范围设为[12,画布宽度-球形精灵黄色1.直径],纵为坐标为[24,画布高度-球形精灵_黄色1.直径]。它们的移动代码如图4.6所示。 让小球定时动起来
小球每隔0.5秒会变换一次位置,这就要求每隔0.5秒这些让小球移动位置的代码需要被执行一次。这有点像每隔一天(24小时)闹钟都会闹铃,催我起床一样。App Inventor中提供了“计时器”组件来实现这种有时间规律的事件处理。
计时器有两个主要属性,“启用计时”属性和闹钟开关一样,决定计时器是否工作。“计时间隔”属性则决定了事件发生的周期,在本例中事件间隔是500,即500毫米,那么这个计时器会每隔0.5秒产生一个计时事件,将执行在“计时”事件处理器中拼入的代码模块。
计时器的工作原理如图4.6所示,每隔一个计时间隔的时间就会产生一个峰值脉冲,在这个峰值时刻会触发计时处理器去执行相应的代码,而峰值之间的时间段则不引发操作。 通过计时器组件,最终让四个小球定时变换位置的代码如图4.7所示。 实现划线功能
点击划线按钮后会在两个黄色小球之间画一条黄色的线。实现这个功能主要是利用了画布所提供的画线方法。因为是需要画出黄色的线条,所以先要把画布的画笔颜色设置为黄色,然后再调用画线方法。按两点确定一条直线的原理,画线需要提供2个端点的坐标,这里只需将两个黄色小球的坐标作为实际参数带入即可。具体代码如图4.8所示。 实现画圆功能
点击画圆按钮后会在粉色小球的当前位置画一个和它大小一致粉色实心圆。利用了画布所提供的画圆方法即可实现这个功能。首先要把画布的画笔颜色设置为粉色,然后再调用画圆方法。画圆需要3个基本参数,即圆心的平面坐标x和y值、圆半径,这里只需将粉色小球的坐标和半径作为实际参数带入。另外,画圆方法有四个参数槽,最后一个“充满”槽缺省拼入一个“true”模块。在“充满”参数槽中需要一个逻辑值,只能拼入“true”或者“false”。当拼入的值为“false”时,画出来的圆是空心圆。具体实现代码如图4.9所示。 实现画文字功能
点击文字按钮后会在红色小球的当前位置画出一个带旋转角度的红色“AnAn”文字。画布组件提供了2种在画布中画出文字的方法。一种是叫“画字”,这种方法画出的文字是不带旋转角度的,还有一种是“沿角度画字”,这个方法多一个参数“角度”,画出来的文字就是带这个角度的旋转角的。在本例中,这个角度设置为12,这样画出的“AnAn”文字方向是为以X轴为起点,逆时针旋转12度的角度,具体效果参见图4.1(b)。画文字的具体实现代码如图4.10所示。 实现画布清屏功能
点击清屏按钮后整个画布会恢复原始状态,前面所画的图案将消失掉。这个功能看上去很神奇,其实实现起来非常简单,直接调用画布组件提供的清屏方法即可。具体实现代码如图4.11所示。
实现画作保存功能
点击保存按钮后会把画布当前的图案保存为一个图像文件,存放在SD卡中,这样就不怕App关闭后创作的画作丢失了。画布组件提供了2种图像保存方法,一种是不指定文件名的方法,还有一种可以由开发者确定文件名的方法,叫做“另存”。这两种实现方法如图4.12所示。 如果是调用“保存”方法,开发人员不能指定保存的图像文件名,系统会给它自动命名。
如果是调用“另存”方法,如上图所示,保存的图像文件名将是“AnAnDrawing.png”。
有一点需要注意,无论是“保存”还是“另存”方法,它们都有一个返回值,这两个方法模块不能直接拼入按钮的被点击事件处理器中。这里需要借助控制组团的一个转接模块“求值但忽视结果”作为桥梁才能拼接上。这有点像家里的插座转接口,实现不同模块的桥接功能。
调用“保存”方法生成的图像文件名的命名规则是由“前缀appinventor加上系统当前时间值”构成,存放的位置是SD卡中的“/My Document/Picture”目录。根据这个文件名很难区分不同App保存的图像。而以上“另存”方法虽然可以由开发者给存储的图像文件命名,但由于文件是在开发阶段已经定死了,因此后面保存的图像文件也会是同一个,这样就会覆盖掉前面保存的文件。
为了避免这种情况,可以结合2种保存方法的优点,即可以由开发者确定一个保存文件命名的规则,做到既个性化,又不会重复。在本例中,将采用“文件名前缀+系统时间”自动合成唯一文件名的方法。
获取系统时间需要通过“计时器”组件来实现。计时器组件不但提供了前面用到的定时促发的计时事件处理入口,还提供了丰富的和时间相关的方法。比如求日期、求两个时间点的时间间隔、求某个时间点是星期几等等。当调用计时器的求系统时间方法后,会返回一个代表系统时间值的长整数,其实时间在计算机内部也是用这个长整数来表示的,随着时间推移,这个数字会不断增加。通过对比图4.13中的文件名,可以看出“保存”和本次“另存”文件名之间的相似性。
“另存”方法保存的文件存放的位置是SD卡的根目录。
具体实现代码如图4.14所示。
也许有的开发者希望保存的文件最好能由用户在保存时输入,这个功能的实现关键是要能获取用户输入的文件名,这个可以通过“对话框”组件来实现。对话框组件提供了不同类型的对话框方法来实现和用户的交互。这里将利用“显示文本对话框”方法来获取用户输入的文件名。具体实现如图4.15所示。
“显示文本对话框”有3个参数槽,分别是消息、标题和是否允许撤销。当保存按钮被点击调用显示文本对话框后,App将弹出一个带输入的对话框,效果如图4.13(c)所示。如果用户输入点击“Cancel”按钮,则什么都不发生,如果点击了“OK”按钮,则转入对话框的“输入完成”事件处理入口,在这里,只需要把用户输入合成为保存的文件名即可。具体代码见图4.15所示。
如果调研“显示文本对话框”方式时“允许撤销”的参数为“false”,则弹出的对话框不会出现“Cancel”按钮。 给出保存成功提示
前面已经实现了保存画布图像文件,但没有给出任何反馈。这种情况可以认为该App的用户友好性不强,用户体验不佳。因为没有任何反馈,用户不知道刚才的保存有没有成功,甚至怀疑有没有点击到保存按钮,可能会连续点很多次。
为了增强用户体检,对一些关键性的操作可以给出提示,比如保存后提示已经保存成功,这样用户就知道操作成效了。弹出提示信息框也可以通过对话框组件来实现。这里只是需要给出一个提示,不需要用户去按确定按钮,因此选择调用“显示告警信息”方法。通过“显示告警信息”显示的提醒信息会在出现一段时间后会自动消失。显示效果如图4.1(g)所示,具体实现如图4.16所示。 前面本例中实现的功能都是通过按钮点击触发的,是针对不同按钮“被点击”事件处理入口进行编程。现在的智能手机的一大特点就是屏幕是触摸屏,因此可以通过对触摸屏不同的接触方式来实现不同的行为。本例中给出了2种典型的触摸屏事件处理案例。
实现画布被触碰功能
当手指点击屏幕,安安高兴的说:“我在这里”,红色小球随之而来。把画笔调成紫红色,在点击处留下AnAn签名,这个签名还是随机旋转的。实现这个功能需要响应画布的被触碰事件。当手机触碰到屏幕又马上离开时会触发这个事件。
在画布的被触碰事件处理器中,有三个传入的参数,分别是触碰点的“x坐标”和“y坐标”,以及“触摸任意精灵”。前两个参数比较好理解,第三个参数实际上是一个逻辑值,只有true和false之分。当手指触碰到画布的同时也触碰到画布上的某个精灵时,则为true,否则为false。请大家不要以为这个参数能告诉你具体是触碰到了哪个精灵。
实现在画布上直接拖屏作画功能
提起在手机上画画,最自然的方式莫过于直接用手指在手机屏幕上拖屏而画。这需要在手指划过的每一点上都留下过往的痕迹。从人的感知中,手指在屏幕上划拖动过程是一个连续的过程的。但计算机处理时实际上是将这个连续的过程分解为密集的离散采样点,就像线段是由点构成的,只要采样的频率够高、点够密集,那么对这些离散点的看起来就像连续的线条。
实现手指在屏幕上拖动作画,需要响应画布的“被拖动”事件。在画布“被拖动”事件处理器中,传入了7个参数。“起点X坐标”和“起点Y坐标”是指手指第一触摸到画布,开始拖动的起点位置坐标;“当前X坐标”和“当前Y坐标”是指当前时间点采集到了手指触摸到画布的位置坐标;“前点X坐标”和“前点Y坐标”是指上个采样时间点手机采集到的手指触摸画布的位置坐标;而“拖动任何精灵”和前面所用的画布“被触碰”事件处理器中的“触摸任意精灵”参数一样,是一个逻辑值,表示是否拖动了某个精灵。
在了解这些参数后,可以通过在“前点坐标”和“当前坐标”之间画上直线,由于采样的时间很快,画布“被拖动”事件也会很密集的触发,这样实际每次画出的直线都非常短,多次短直线画出来的效果就是任意曲线了。直接用手指在画布上拖动作画的代码见图4.18。这里画布的画笔颜色被设置为紫色。
至此一个画板App就基本完成了。
自定义画笔颜色
尽管刚才开发完成的画板App已经具有了较为丰富的功能,能画出美丽的图画。但由于画布的画笔颜色都是固定的,而且在逻辑设计中的“颜色”模组中可选的颜色种类非常少,因此可以进一步完善,自己开发一个调色板功能,通过RGB(红、绿、蓝)三种基色来调出个性化色彩。
由于在Screen1中屏幕基本被画布和多个按钮填满了,本例将增加一个新的屏幕,通过多个屏幕之间的调用来扩展功能。一般一个成熟的App都会由多个屏幕构成,比如一个游戏App,除了主屏幕外,还可能会有选关、分数排行榜等屏幕。
设计调色板屏幕界面
手续需要新建一个屏幕,点击开发界面上方的增加屏幕按钮,会弹出一个新建屏幕对话框,如图4.19所示,把屏幕名称修改为“Screen_SelectColor”。注意:虽然一般组件的命名可以用中文,但屏幕不能用中文命名。
组件设计如图4.20所示,在新的屏幕中拖入1个画布组件、3个滑动条组建和1个按钮组件,并按表4.2设置各组件的属性。
“滑动条”组件是一种通过移动滑块位置来确定所选数值的可视化组件,有最大值和最小值2个属性要根据需求来设置。在App Inventor内置块的“颜色”组件中提供了一个“合成颜色”的方法,通过设置红、绿、蓝三基色的色值来合成所需颜色,这个色值的取值范围为0~255,因此将滑动条的最大值和最小值分别设置为255和0。另外,为了更加直观,代表不同颜色的滑动条右侧颜色都设置为对应的颜色。
当有滑动条的滑块被移动时,颜色预览画布将把背景色设置为新合成的颜色,这样就实现了最直观的调色效果。图4.21显示的是“滑动条红”的滑块”“位置被改变”事件响应代码。类似的,“滑动条绿”和“滑动条_蓝”的“位置被改变”事件响应代码都相同,即把画布的背景颜色设置为新合成的颜色。 实现屏幕调用和返回
Screen_SelectColor屏幕的代码完成后,需要在Screen1屏幕中点击“选择画笔颜色”按钮来调用Screen_SelectColor屏幕。屏幕调用也是一种程序结构控制,在逻辑设计开发界面的内置块中,“控制”组团提供了2种屏幕调用方法。一种是“打开屏幕”方法,还有一种是“打开屏幕并传值”方法,差别在于是否要传一个值给被打开的屏幕。在App Inventor里面,一个屏幕是不能访问另外一个屏幕的组件或者变量的。如果要实现屏幕之间的值传递,需要通过特定的方法来实现。
在本例中,由于Screen1打开Screen_SelectColor并不需要传值给它,所以只需调用“打开屏幕”方法即可,实现代码见图4.22。
当在Screen_SelectColor屏幕中选选好颜色返回Screen1中时,这时候需要把选定的颜色传回给Screen1。因此在Screen_SelectColor中点击“返回”按钮的事件响应中需要关闭自身并传递一个返回值。具体实现代码如图4.23所示。 那么问题又来了,在Screen1中如何收到这个值?
为了接收返回值,首先需要定义一个变量“画笔颜色”,收到返回值后把这个“画笔颜色”变量的值设为接收到的颜色值。在Screen1中为获取上一个关闭并且返回Screen1屏幕的返回值,需要调用“关闭屏幕”方法。“关闭屏幕”方法传入了2个参数,1个上一个关闭的屏幕名称,还有1个就是上个关闭屏幕所传回的返回值。在本例中,由于不需要关心是哪个屏幕关闭传回的值,因此可以直接把返回结果付给“画笔颜色”变量。具体实现代码见图4.24。 得到传回的颜色值后,下一步就可以改造原来作画功能的画笔颜色设置模块,把画布的画笔颜色设置为变量“画笔颜色”的值就OK了。比如画布“被拖动”的事件处理代码修改如图4.25所示。 完善屏幕初始化代码
整个App的开发接近尾声了,如果运行起来,还会发现一个小瑕疵:就是当从Screen1打开调色板屏幕时,调色板屏幕的画布颜色是白色的,和三个滑动条的滑块所指示的合成颜色不符合。这个可以通过在Screen_SelectColor的“初始化”事件处理里来完善。具体实现的代码见图4.26。这样每次打开Screen_SelectColor时,都会执行这段代码,让画布的背景色和合成颜色一致。
需要多个屏幕的原因
很少有App能够将全部内容都放在一个屏幕中。例如,对于新闻App,你通常需要单机标题或者链接,而它们会将你带到第二个界面上。
警告:一旦给第二屏幕设置了名称,你就无法再修改它了。
幸运的是,屏幕的名称对于用户并不可见,只要你知道它的含义就可以了。
多屏幕有助于分割功能。每个屏幕应该包括一组会一起使用的特性和功能,并且与其他屏幕上的特性有较大差别。
多屏幕可以共享媒体组件,例如声音和图片。举例来说,你上次的并且在Screen1中使用的素材也可以在Screen2中使用。
多屏幕的问题 多屏幕使得App更加复杂,增加了出现缺陷和错误的可能性。
在实时开发模式中,屏幕切换需要花费很长时间。这包括使用AI伴侣、USB连接模拟器。当在实时开发模式中切换屏幕时,Designer必须加载新屏幕所需的所有组件和块,将它们推送到手机上。这让测试变得困难且慢。当把App构建成Android软件包并安装之后,屏幕切换会快一些。
在屏幕之间共享信息并不容易,这取决于你的设计。添加多屏幕仅能在适当的情况下工作,因此要确保这样配置App是在最大程度上考虑了用户的感受。
多屏幕之间无法共享组件、布局或者任何块,包括变量。当创建新屏幕时,它就像一个全新的App。你在一个屏幕中编写的任何代码在另一个屏幕中都要重写。
警告:Screen1不能被重命名
你的App最初总是会展现Screen1。你无法重命名或者删除Screen1,而且也无法使用其他屏幕代替它。 当初此使用多屏幕时,许多App Inventor程序员想要为已有App添加一个欢迎字幕,他们惊讶地发现欢迎屏幕需要在Screen1中,但已经来不及修改它了。
屏幕的行为就像放在桌子上的一小叠卡片。你最初只有一张卡片,即首先看到的屏幕Screen1。当打开另外一个屏幕时,该屏幕会在前一个屏幕之上展开,就如同在卡片上又放置了另外一张。
这个卡片栈模型最大的优点就是之前的屏幕仍然存在,它只是在当前被隐藏并挂起来了。返回到第一个屏幕的正确方法是使用close screen,它从栈中移除当前卡片并露出位于下面的前一张卡片。
在屏幕之间共享数据
多个屏幕就像一个个独立的App,你可以在其间切换。他们不共享变量和块,因此从一个屏幕获取来自另一个屏幕的信息需要一些辅助工具。
跨屏幕获取数据的一种较简便的方法是使用tinyDB。你在TinyDB中保存的任何数据在所有屏幕中都是可用的。TinyDB在手机上(而非App自身中)保存其数据,因此当App关闭或者屏幕切换之后,数据仍然存在。