当前位置: 首页 > 科技观察

面向大众的移动技术- Overheard Word 的单词和手势

时间:2023-03-21 00:56:51 科技观察

面向大众的移动技术:无意中听到Word的语言和手势kidin,但是将此代码与您的AndroidUI集成仍然需要一些技巧。本月,AndrewGlover将向您展示如何使用基于JSON的文字引擎和一些预制的滑动手势功能将OverheardWord演示应用程序提升到一个新的水平。事实证明,Android可以轻松容纳第三方代码,但如果您希望应用程序的UI流畅运行,您仍然必须实施一些谨慎的逻辑。如果到目前为止您已经阅读并遵循了本系列中的演示,那么您已经掌握了一些基本的Android开发技能。除了设置Android开发环境和编写您的第一个HelloWorld应用程序之外,您还学习了如何将按钮点击替换为滑动手势并实现菜单(或工具栏)和图标。在本文中,您将继续这条轨迹,学习如何使用第三方库来增加或增强应用程序的功能。首先,我们将安装一些开源库和读取文件,然后我们将以编程方式将新功能与演示应用程序的UI集成。正如我在之前的文章中所做的那样,我将使用我的OverheardWord应用程序进行演示。如果您尚未克隆OverheardWord的GitHub存储库,则应先克隆,以便执行后续步骤。关于本系列移动应用程序发布呈爆炸式增长,而移动开发技术恰逢其时。本系列文章将向有编程经验但刚接触移动领域的开发者介绍该领域的发展。本系列从使用Java代码编写本机应用程序开始,然后扩展您的工具箱以包括JVM语言、脚本框架、HTML5/CSS/JavaScript、第三方工具等。您将培养满足几乎所有移动开发场景需求所需的技能。Thingamajig:可插入的单词引擎OverheardWord是一种英语应用程序,可帮助用户学习新单词并动态构建词汇表。在之前的文章中,我们首先开发了一个基本的应用程序,然后添加了滑动手势以便于导航,并添加了图标以获得更漂亮的UI。到目前为止还不错,但是这个应用程序不会走得太远,因为它缺少某种成分:OverheardWord需要一些词!为了使OverheardWord成为真正的多词应用程序,我构建了一个小型词引擎Thingamajig,它封装了词的概念及其相应的定义。Thingamajig处理从JSON文档创建单词及其定义,并且完全不依赖于Android。与OverheardWord应用程序一样,MyWordEngine托管在GitHub上。您可以克隆存储库,或下载源代码,然后运行??ant。您将获得一个轻量级的8KBJAR文件,您可以将其复制到您的libs目录中。如果您的Android项目设置正确,IDE会自动将libs目录中的任何文件识别为依赖项。当时,我的单词引擎中的代码是用一个JSON文档实例初始化的。JSON文档可以是驻留在设备文件系统中的文件、对HTTP请求的响应,甚至是对数据库查询的响应——目前,这并不重要。重要的是你有一个有用的库,可以让你使用Word对象。每个Word对象都包含一组Definition对象和一个相应的词性。虽然单词与其定义之间的关系远非简单,但现在是可行的。之后,我们可以添加例句、同义词和反义词。Android中的第三方库将第三方库集成到Android项目中很容易;事实上,每个Android启动项目都包含一个特殊的目录libs,您可以在其中放置第三方JAR。在Android构建过程中,普通JVM文件和第三方JAR都会被转换为与Dalvik(专用AndroidVM)兼容。环境限制虽然您可以想象将您喜欢的任何第三方库添加到您的应用程序,但永远不要忘记移动环境的限制。运行您的应用程序的设备毕竟不是某个可靠的Web服务器!用户将欣赏您改进数据处理和最小化第三方加载项下载大小的能力。除了将单词引擎库插入我的应用程序之外,我还将添加另一个名为Gesticulate的第三方应用程序。与单词引擎库一样,您可以通过从GitHub克隆或下载其源代码来获取Gesticulate。然后运行??ant,您将获得一个JAR文件,您可以将其放入应用程序的libs目录中。更新UI现在,我们进入本文的核心,更新UI以免费集成您刚刚获取的所有第三方代码。幸运的是,我使用OverheardWord应用程序提前计划了这一刻。当我编写应用程序的第一次迭代时,我定义了一个简单的布局,其中包括一个单词、它的词性和定义,如图1所示:图1.OverheardWord的默认视图。布局是使用占位符文本定义的,我将用从我的可插入文字引擎中获取的实际文字替换它。所以,我会初始化一个单词数组,获取其中一个,然后用它的值相应地更新UI。为了更新UI,我必须能够获取每个视图元素的句柄并为这些元素提供值。例如,一个显示的词(如Pedestrian)被定义为一个ID为word_study_word的TextView,如清单1所示:清单1.一个布局中定义的TextView如果仔细观察,我在XML中将文本设置为“Word”;但是,我可以通过编程方式获取对TextView实例的引用,并调用setText来设置该值。在继续下一步之前,我需要构建一个单词列表。您可能还记得我的文字引擎可以从JSON文档创建Word实例,所以我所要做的就是设置我的Android应用程序以包含和读取这些自定义文件。使用文件:res和raw目录默认情况下,Android应用程序有权读取设备的底层文件系统,但您只能访问应用程序本身下的本地文件系统。因此,您可以在应用程序中包含文件并相应地引用它们。(这意味着您可以读写应用程序本地的文件;写入应用程序外部的文件系统,例如SD卡,需要特殊权限。)因为我的单词引擎可以拿一个JSON文档来初始化单词列表,所以没有什么能阻止我包含应用程序将在运行时读取的JSON文档。就像我之前文章中演示的图标资源一样,你可以将文件存放在res目录下。如果我发现自己需要自定义文件,我喜欢将它们添加到名为raw的目录中。我放在该目录中的任何文件都可以通过生成的R文件进行引用,我在OverheardWord中使用过几次。基本上,Android平台从res目录中获取资产并创建一个名为R的类,然后为这些资产提供句柄。如果资产是一个文件,R文件将提供一个引用来打开文件并获取其内容。我在res目录结构中创建了一个raw目录,并在该目录下放了一个JSON文档,如图2所示:图2.带有新词的raw目录接下来,Eclipse重建项目和我的R文件新文件可以参考很容易,如图3所示:图3.更新后的R文件一旦我有了一个文件的句柄,我就可以打开它,读取它,最后构造一个JSON文档作为生成单词列表的基础。构建单词列表当应用程序启动时,我开始执行一系列步骤来加载原始JSON文档并构建单词列表。我将创建一个名为buildWordList的方法来处理这些步骤,如清单2所示:清单2.在AndroidprivateListbuildWordList(){InputStreamresource=getApplicationContext().getResources().openRawResource(R.raw.words);Listwords=newArrayList();try{StringBuildersb=newStringBuilder();BufferedReaderbr=newBufferedReader(newInputStreamReader(resource));Stringread=br.readLine();while(读!=null){sb.append(read);read=br.readLine();}JSONObjectdocument=newJSONObject(sb.toString());JSONArrayallWords=document.getJSONArray("words");for(inti=0;iwords=buildWordList();WordStudyEngineengine=WordStudyEngine.getInstance(words);当有一个初始化的引擎实例时,我可以向它询问一个词(自动随机播放),然后相应地更新UI的三个元素。例如,我可以更新定义的单词部分,如清单5所示:清单5.以编程方式更新UI元素WordaWord=engine.getWord();TextViewwordView=(TextView)findViewById(R.id.word_study_word);wordView。setText(aWord.getSpelling());清单5中的findViewById是一个读取整数ID的Android平台调用,您可以从应用程序的R类中获取该ID。您可以通过编程方式将文本设置为TextView。您还可以设置字体类型、字体颜色或文本显示大小,类似于:wordView.setTextColor(Color.RED)。在清单6中,我基本上遵循相同的过程来更新应用程序UI的定义和词性元素:清单6.更多编程更新DefinitionfirstDef=aWord.getDefinitions().get(0);TextViewwordPartOfSpeechView=(TextView)findViewById(R.id.word_study_part_of_speech);wordPartOfSpeechView.setText(firstDef.getPartOfSpeech());TextViewdefView=(TextView)findViewById(R.id.word_study_definition);defView.setText(formatDefinition(aWord));在清单6中,如何使用R文件按名称引用布局元素。驻留在我的Activity类中的formatDefinition方法读取定义字符串并将其第一个字母大写。该方法还格式化字符串,以便在句子末尾没有句点时在句末使用句点。(请注意,Thingamajig与格式化无关——它只是一个文字引擎!)我已经完成了这些UI元素的更新,所以我可以启动我的应用程序并检查结果。看!我现在要学习一个法律词汇!图4.OverheardWord有文字!添加手势:将滑动连接到单词现在我可以有效地显示单词,我想让用户能够快速滑动我的单词引擎中的所有单词。为了让事情变得更简单,我将使用Gesticulate,这是我自己的第三方库,可以计算滑动速度和方向。我还将滑动逻辑放入了一个名为initializeGestures的方法中。初始化滑动手势后,第一步是将显示单词的逻辑移动到一个新方法中,我可以调用该方法在有人滑动时显示新单词。更新后的onCreate方法(最初在Android创建应用程序实例时调用)如清单7所示:清单7。初始化手势时,onCreate可以显示单个单词protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);Log.d(APP,"onCreatedInvoked");setContentView(R.layout.activity_overheard_word);initializeGestures();Listwords=buildWordList();如果(engine==null){engine=WordStudyEngine.getInstance(words);}WordfirstWord=engine.getWord();displayWord(firstWord);}请注意引擎变量,我将其定义为OverheardWordActivity本身的私有静态成员变量。我将简要解释为什么这样做。接下来,我将介绍initGestureDetector方法,如果您遵循克隆的代码,它会在initializeGestures方法中被引用。如果您还记得,initGestureDetector方法有一个逻辑,当用户在设备屏幕上向上、向下、向左或向右滑动时执行操作。在OverheardWord中,当用户从右向左滑动(这是向左滑动)时,我想显示一个新词。我首先删除Toast消息,这是此代码的占位符,并将其替换为对displayWord的调用,如清单8所示:清单8.initGestureDetector在向左滑动时显示一个词){publicbooleanonFling(MotionEvente1,MotionEvente2,floatvelocityX,floatvelocityY){try{finalSwipeDetector=newSwipeDetector(e1,e2,velocityX,velocityY);if(detector.)){returnfalse;}elseif(detector.isLeftSwipe()){displayWord(engine.getWord());}elseif(detector.isRightSwipe()){Toast.makeText(getApplicationContext(),"RightSwipe",Toast.LENGTH_SHORT).show();}}catch(Exceptione){}返回假;}});}我的单词引擎由engine变量表示,需要在整个Activity中访问它。这就是为什么我将它定义为一个成员变量,以确保每次向左滑动时都会显示一个新单词。来回滑动现在,当打开应用程序并开始滑动时,每次向左滑动时,我都会看到一个新的单词和定义显示出来。但是,当向其他方向滑动时,我只会收到一条我已滑动的小消息。如果我可以通过向右滑动返回到上一个单词,那不是很好吗?倒退似乎并不难。也许我可以使用一个堆栈,然后弹出前一次向左滑动放在那里的***元素?但是,当用户返回后再次向左滑动时,这种想法就站不住脚了。如果弹出***的位置,将显示一个新词,而不是用户以前见过的词。在考虑之后,我倾向于尝试一种链表方法,该方法可以在用户来回滑动时摆脱以前浏览过的单词。我将首先创建一个包含所有显示单词的LinkedList,如清单9所示。然后,我将保留一个指向该列表中元素索引的指针,我可以使用它来检索我看到的单词。当然,我会将这些定义为静态成员变量。我还将我的指针初始化为-1,每当一个新词被添加到LinkedList实例时,我都会递增它。与任何语言中的大多数支持数组的集合一样,LinkedList是从零开始索引的。清单9.新成员变量privatestaticLinkedListwordsViewed;privatestaticintviewPosition=-1;在清单10中,我在应用程序的onCreate中初始化了LinkedList:清单10.初始化LinkedListif(wordsViewed==null){wordsViewed=newLinkedList();}现在,当显示第一个单词时,我需要将它添加到LinkedList实例(wordsViewed)并递增指针变量viewPosition,如清单11所示:清单11.不要忘记递增视图位置WordfirstWord=engine.getWord();wordsViewed.add(firstWord);查看位置++;显示字(第一个字);幻灯片的逻辑现在,我进入逻辑的主要部分,这需要一些思考。当用户向左滑动时我想显示下一个词,当他或她向右滑动时我想显示上一个词。如果用户再次向右滑动,则应显示前第二个单词,依此类推。这应该一直持续到用户返回到第一个显示的单词。然后,如果用户再次开始向左滑动,单词列表应该以与之前相同的顺序出现。***部分有点棘手,因为我的WordStudyEngine的默认实现是以随机顺序返回单词。在清单12中,我觉得我已经解决了我想做的事情的逻辑。第一个布尔值赋值包含在一个名为listSizeAndPositionEql的方法中,该方法很简单:wordsViewed.size()==(viewPosition+1)。如果listSizeAndPositionEql被指定为true,应用程序将通过displayWord方法显示一个新词。但是,如果listSizeAndPositionEql为false,则用户必须向后滑动;因此,使用viewPosition指针从列表中检索相应的单词。清单12.用于来回滚动的LinkedListpublicbooleanonFling(MotionEvente1,MotionEvente2,floatvelX,floatvelY)isUpSwipe()){returnfalse;}elseif(detector.isLeftSwipe()){if(listSizeAndPositionEql()){viewPosition++;Wordwrd=engine.getWord();wordsViewed.add(wrd);displayWord(wrd);}elseif(wordsViewed.size()>(viewPosition+1)){if(viewPosition==-1){viewPosition++;}displayWord(wordsViewed.get(++viewPosition));}else{returnfalse;}}elseif(detector.isRightSwipe()){if(wordsViewed.size()>0&&(listSizeAndPositionEql()||(viewPosition>=0))){displayWord(wordsViewed.get(--viewPosition));}else{returnfalse;}}}catch(Exceptione){}returnfalse;}请注意,如果viewPosition为-1,则用户一直滑动回到起点——在这种情况下,列表中的指针递增回到0。这是因为在基于Java的LinkedList实现中没有-1元素。(在其他一些语言中,负位置值从列表的后面开始,因此-1是尾部元素。)一旦掌握了向左滑动,处理向右滑动逻辑就相当容易了。实际上,如果您在wordsViewed实例中有一个单词,那么您需要使用一个递减的指针值来访问之前查看过的单词。尝试一下:启动OverheardWord实例并来回滑动以学习一些单词。每次滑动时,用户界面都会相应更新。结论OverheardWord运行良好,现在我们已经开始在基本的Androidshell之上添加一些更有趣的功能。当然,还有更多,但我们现在有一个功能正常的应用程序,可以处理滑动、有图标和菜单,甚至可以从自定义的第三方word文件中读取。下个月,我将向您展示如何向OverheardWord添加更多样式,然后实现灵活的测验功能。