前言:

自学android差不多两个月了,由于本身对英语不感冒,而且记英语单词总是很快忘记,因此学习的过程也是蛮累的,好多类和方法都不知道啥意思,还要去查词典才知道。
还是延续我读书时的记忆方法——每次遇到生词就写在笔记本上,下次在遇到就算不记得中文意思,也能记得写过这个单词,然后就是找笔记本就可以了。不过那,这种方法也有个问题——自己的字太丑,每次都是找了好久都没找那个词,其实明明在哪里,只是快速扫看不到o(╯□╰)o。
后来,就想找一个背单词的app,可以把我不认识的生词添加到一个生词本,可以快速浏览生词本里的单词,也可以仅仅针对生词本里的词出一些帮助记忆的练习题?又想,既然我在学android,为什么不自己做一个那?于是就有了这个项目练习!

项目实现:

我在网上找到了一个前辈分享的类似的app编写过程,发现其中很多内容都是我会的,于是我就参考着自己动手写起来。
由于这个项目不是完成后才开始写这篇博客,是我边实践边写的,因此整体思路是根据我的写代码进度来的,在写这里的时候刚实现了查单词的界面和完整功能。

查词界面:

先来看下这个界面的功能和实现思路:
(一)肯定是要能查单词
简单的实现思路就是使用现有词典的API接口,我采用的是金山词霸的API接口,地址:http://open.iciba.com/。优点是这个接口会返回发音MP3的http地址。
查词接口:http://dict-co.iciba.com/api/dictionary.php?w=go&key=** 这里的key是你自己申请的金山词霸开放平台的API key。
打开后是这样的:

<dict num="219" id="219" name="219">
<key>go</key>
<ps>gəʊ</ps>
<pron>http://res.iciba.com/resource/amp3/0/0/34/d1/34d1f91fb2e514b8576fab1a75a89a6b.mp3</pron>
<ps>goʊ</ps>
<pron>http://res.iciba.com/resource/amp3/1/0/34/d1/34d1f91fb2e514b8576fab1a75a89a6b.mp3</pron>
<pos>vi.</pos><acceptation>走;离开;去做;进行;
</acceptation>
<pos>vt.</pos>
<acceptation>变得;发出…声音;成为;处于…状态;</acceptation>
<pos>n.</pos>
<acceptation>轮到的顺序;精力;干劲;尝试;</acceptation>
<sent><orig>
Go is an irregular verb.
</orig><trans>
go是个不规则动词.
</trans></sent><sent><orig>
Kyong - go means a warning or half - point deduction and gam - jeom means a one - point deduction.
</orig><trans>
Kyoug -go是指一次警告或被扣减半分, gam -jeom是指被扣减1分.
</trans></sent><sent><orig>
From the get - go means from the beginning.
</orig><trans>
原来fromtheget-go 就是一开始的时候.
</trans></sent><sent><orig>
With the reduction of SRWC, GO activity decreased mild water stress and increased water stress.
</orig><trans>
随着土壤相对含水量的下降,GO酶括性在土壤水分含量下降时首先降低,以后又逐渐上升.
</trans></sent><sent><orig>
We proved orthocompactness and weakly suborthocompactness are equivalent for all subspaces of product of two GO - space.
</orig><trans>
证明了GO - 空间子空间的正交紧性和弱子正交紧性是等价的.
</trans></sent></dict>

因此这里就要用到Http网络访问和XML解析。为避免重复访问网络,我们可以将解析出来单词的数据保存在本地,这样下次在查到该词是可以直接从本地读取了,同样的我们可以直接把MP3文件也保存本地。
分析完成后开始动手,首先按功能分模块,这方面由于我是新手,就是按照自己看的清晰的方式来,新建一个util包,这里都是放一些工具类。然后新建一个类HttpUtil,通过HttpURLConnection实现网络访问功能:

public class HttpUtil {/*** 在新线程中发送网络请求** @param address  网络地址* @param listener HttpCallBackListener接口的实现类;*                 onFinish方法为访问成功后的回调方法;*                 onError为访问不成功时的回调方法*/public static void sentHttpRequest(final String address, final HttpCallBackListener listener) {new Thread(new Runnable() {@Overridepublic void run() {HttpURLConnection connection = null;try {URL url = new URL(address);connection = (HttpURLConnection) url.openConnection();connection.setRequestMethod("GET");connection.setConnectTimeout(8000);connection.setReadTimeout(8000);InputStream inputStream = connection.getInputStream();if (listener != null) {listener.onFinish(inputStream);}} catch (IOException e) {e.printStackTrace();if (listener != null) {listener.onError();}} finally {if (connection != null) {connection.disconnect();}}}}).start();}
}

android网络访问不能在UI线程中进行,避免阻塞,因此,这里我直接在新线程实现,根据目的需要,完成网络请求后要对返回的XML文件进行解析,因此方法第二参数传入HttpCallBackListener接口的实现类,分别对应onFinish方法为访问成功后的回调方法,onError为访问不成功时的回调方法。

接下来是XML解析了,我采用的是SAX解析方法。
我们先分析下XML文件,看看有哪些节点:
key:单词本身; ps:第一个是英音音标,第二个是美音音标; pron第一个是英音的MP3地址,第二个是美音的;pos 词性; acception 词义;sent 例句; orig例句英语;trans例句中文翻译。
这个api接口也可以查中文,只需要在待查的词前面加上一个下划线 _ 即可,如 :_你好。

<dict num="219" id="219" name="219">
<key>你好</key>
<fy>Hello</fy>
<sent>
<orig>Hello! Hello! Hello! Hello! Hel - lo!</orig>
<trans>你好! 你好! 你好! 你好! 你好!</trans>
</sent>
<sent>
<orig>Hello! Hello! Hello! Hello ! I'm glad to meet you.</orig>
<trans>你好! 你好! 你好! 你好! 见到你很高兴.</trans>
</sent>
<sent>
<orig>Hello Marie. Hello Berlioz. Hello Toulouse.</orig>
<trans>你好玛丽, 你好柏里欧, 你好图鲁兹.</trans>
</sent>
<sent>
<orig>
B Hi Gao. How are you doing? It's good to meet you.
</orig>
<trans>B你好,高. 你好 吗 ?很高兴认识你.</trans>
</sent>
<sent>
<orig>
Grant: Hi , Tess. Hi , Jenna. Are you doing your homework?
</orig>
<trans>格兰特: 你好! 苔丝. 你好! 詹娜. 你们在做家庭作业 吗 ?</trans>
</sent>
</dict>

可以看到查中文的话会多一个属性:fy 即中文的英文翻译,要一起考虑进去。

因为要把查到单词的内容保存本地,我们就要建一个Words类用来管理xml解析出来的内容,新建一个model包,在其下新建一个Words类:

public class Words {//中英文标记private boolean isChinese;//要翻译的单词,可以是中文;private String key;//key为中文时的翻译private String fy;//英音发音private String psE;//英音发音的mp3地址private String pronE;//美音发音private String psA;//美音发音的mp3地址private String pronA;//单词的词性与词义private String posAcceptation;//例句private String sent;public Words() {this.key = "";this.fy = "";this.psE = "";this.pronE = "";this.psA = "";this.pronA = "";this.posAcceptation = "";this.sent = "";this.isChinese = false;}public Words(boolean isChinese, String key, String fy, String psE,String pronE, String psA, String pronA, String posAcceptation, String sent) {this.isChinese = isChinese;this.key = key;this.fy = fy;this.psE = psE;this.pronE = pronE;this.psA = psA;this.pronA = pronA;this.posAcceptation = posAcceptation;this.sent = sent;}public boolean getIsChinese() {return isChinese;}public void setIsChinese(boolean isChinese) {this.isChinese = isChinese;}public String getKey() {return key;}public void setKey(String key) {this.key = key;}public String getFy() {return fy;}public void setFy(String fy) {this.fy = fy;}public String getPsE() {return psE;}public void setPsE(String psE) {this.psE = psE;}public String getPronE() {return pronE;}public void setPronE(String pronE) {this.pronE = pronE;}public String getPsA() {return psA;}public void setPsA(String psA) {this.psA = psA;}public String getPronA() {return pronA;}public void setPronA(String pronA) {this.pronA = pronA;}public String getPosAcceptation() {return posAcceptation;}public void setPosAcceptation(String posAcceptation) {this.posAcceptation = posAcceptation;}public String getSent() {return sent;}public void setSent(String sent) {this.sent = sent;}
}

其中只包含一些成员变量,对应我们需要的内容,还有各自的get()和set()方法。

接着在util包下新建一个WordsHandler类继承自DefaultHandler,这个类中解析XML内容成一个words对象:

public class WordsHandler extends DefaultHandler {//记录当前节点private String nodeName;private Words words;//单词的词性与词义private StringBuilder posAcceptation;//例句private StringBuilder sent;/*** 获取解析后的words对象*/public Words getWords() {return words;}//开始解析XML时调用@Overridepublic void startDocument() throws SAXException {//初始化words = new Words();posAcceptation = new StringBuilder();sent = new StringBuilder();}//结束解析XML时调用@Overridepublic void endDocument() throws SAXException {//将所有解析出来的内容赋予wordswords.setPosAcceptation(posAcceptation.toString().trim());words.setSent(sent.toString().trim());}//开始解析节点时调用@Overridepublic void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {nodeName = localName;}//结束解析节点时调用@Overridepublic void endElement(String uri, String localName, String qName) throws SAXException {//在读完整个节点后换行if ("acceptation".equals(localName)) {posAcceptation.append("\n");} else if ("orig".equals(localName)) {sent.append("\n");} else if ("trans".equals(localName)) {sent.append("\n");sent.append("\n");}}//获取节点中内容时调用@Overridepublic void characters(char[] ch, int start, int length) throws SAXException {String a = new String(ch, start, length);//去掉文本中原有的换行for (int i = start; i < start + length; i++) {if (ch[i] == '\n')return;}//将节点的内容存入Words对象对应的属性中if ("key".equals(nodeName)) {words.setKey(a.trim());} else if ("ps".equals(nodeName)) {if (words.getPsE().length() <= 0) {words.setPsE(a.trim());} else {words.setPsA(a.trim());}} else if ("pron".equals(nodeName)) {if (words.getPronE().length() <= 0) {words.setPronE(a.trim());} else {words.setPronA(a.trim());}} else if ("pos".equals(nodeName)) {posAcceptation.append(a);} else if ("acceptation".equals(nodeName)) {posAcceptation.append(a);} else if ("orig".equals(nodeName)) {sent.append(a);} else if ("trans".equals(nodeName)) {sent.append(a);} else if ("fy".equals(nodeName)) {words.setFy(a);words.setIsChinese(true);}}
}

在这里,如何对解析出来的文本重新排版换行这个问题卡了我好几个小时,最后终于找到解决方法,我在昨天的一篇博客中有分享:SAX解析中换行问题解决 。

接着,同样是util包下新建一个ParseXML类,作为解析XML的工具类:

public class ParseXML {/*** 使用SAX解析XML的方法*/public static void parse(DefaultHandler handler, InputStream inputStream) {try {InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");BufferedReader reader = new BufferedReader(inputStreamReader);SAXParserFactory factory = SAXParserFactory.newInstance();XMLReader xmlReader = factory.newSAXParser().getXMLReader();xmlReader.setContentHandler(handler);xmlReader.parse(new InputSource(reader));} catch (Exception e) {e.printStackTrace();}}
}

这里应该没什么问题,调用SAXParserFactory.newInstance()获得SAXParserFactory的实例,再调用newSAXParser().getXMLReader()获得XMLPreader实例,setContentHandler()传入自定义的解析类WordsHandler,最后调用parse()方法,传入inputStream包装成的BufferReader开始解析。

解析后我们可以调用WordsHandler的getWords获得所查单词对应的words对象,接下来可以用SQLite保存在本地。新建一个db包,在db包中新建一个WordsSQLiteOpenHelper类继承自SQLiteOpenHelper,在这个类中新建words库:

public class WordsSQLiteOpenHelper extends SQLiteOpenHelper {/**建表语句*/private String CREATE_WORDS = "create table Words(id Integer primary key autoincrement," +"isChinese text,key text,fy text,psE text,pronE text,psA text,pronA text,posAcceptation text,sent text)";public WordsSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {super(context, name, factory, version);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_WORDS);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

目前没有升级数据的需要,因此仅写了创建数据库的代码。

接下来是一个大类WordsAction,我把它放在util包下,这个类里包含了大部分查词界面所用到的方法,包括:保存words到数据库、获取address地址、向数据库中查找words、保存发音mp3文件、播放发音MP3。
发音MP3我们后面再看,先来看数据库这块:

public class WordsAction {/*** 本类的实例*/private static WordsAction wordsAction;/*** Words的表名*/private final String TABLE_WORDS = "Words";/*** 数据库工具,用于增、删、该、查*/private SQLiteDatabase db;private MediaPlayer player = null;/*** 私有化的构造器*/private WordsAction(Context context) {WordsSQLiteOpenHelper helper = new WordsSQLiteOpenHelper(context, TABLE_WORDS, null, 1);db = helper.getWritableDatabase();}/*** 单例类WordsAction获取实例方法** @param context 上下文*/public static WordsAction getInstance(Context context) {//双重效验锁,提高性能if (wordsAction == null) {synchronized (WordsAction.class) {if (wordsAction == null) {wordsAction = new WordsAction(context);}}}return wordsAction;}/*** 向数据库中保存新的Words对象* 会先对word进行判断,为有效值时才会保存** @param words 单词类的实例*/public boolean saveWords(Words words) {//判断是否是有效对象,即有数据if (words.getSent().length() > 0) {ContentValues values = new ContentValues();values.put("isChinese", "" + words.getIsChinese());values.put("key", words.getKey());values.put("fy", words.getFy());values.put("psE", words.getPsE());values.put("pronE", words.getPronE());values.put("psA", words.getPsA());values.put("pronA", words.getPronA());values.put("posAcceptation", words.getPosAcceptation());values.put("sent", words.getSent());db.insert(TABLE_WORDS, null, values);values.clear();return true;}return false;}/*** 从数据库中查找查询的words** @param key 查找的值* @return words 若返回words的key为空,则说明数据库中没有该词*/public Words getWordsFromSQLite(String key) {Words words = new Words();Cursor cursor = db.query(TABLE_WORDS, null, "key=?", new String[]{key}, null, null, null);//数据库中有if (cursor.getCount() > 0) {Log.d("测试", "数据库中有");if (cursor.moveToFirst()) {do {String isChinese = cursor.getString(cursor.getColumnIndex("isChinese"));if ("true".equals(isChinese)) {words.setIsChinese(true);} else if ("false".equals(isChinese)) {words.setIsChinese(false);}words.setKey(cursor.getString(cursor.getColumnIndex("key")));words.setFy(cursor.getString(cursor.getColumnIndex("fy")));words.setPsE(cursor.getString(cursor.getColumnIndex("psE")));words.setPronE(cursor.getString(cursor.getColumnIndex("pronE")));words.setPsA(cursor.getString(cursor.getColumnIndex("psA")));words.setPronA(cursor.getString(cursor.getColumnIndex("pronA")));words.setPosAcceptation(cursor.getString(cursor.getColumnIndex("posAcceptation")));words.setSent(cursor.getString(cursor.getColumnIndex("sent")));} while (cursor.moveToNext());}cursor.close();} else {Log.d("测试", "数据库中没有");cursor.close();}return words;}

这是一个单例类,我采用了双重锁的方式,提高性能。
方法说明都在代码中有。
这个类中还有一个方法,用于获取Http访问的地址:

/*** 获取网络查找单词的对应地址** @param key 要查询的单词* @return address 所查单词对应的http地址*/public String getAddressForWords(final String key) {String address_p1 = "http://dict-co.iciba.com/api/dictionary.php?w=";String address_p2 = "";String address_p3 = "&key=E568F04171398072F7EC5D8B4A6CBDB4";if (isChinese(key)) {try {//此处非常重要!对中文的key进行重新编码,生成正确的网址address_p2 = "_" + URLEncoder.encode(key, "UTF-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}} else {address_p2 = key;}return address_p1 + address_p2 + address_p3;}

看到代码中“此处非常重要!”的提示那段没,这也是一个卡了我几个小时的问题。
原本我是这样写的:

address_p2 = "_"+key;

问题是在查询中文的时候得不到任何数据,我还打印了访问的网址,Log出来的地址,我复制到ie浏览器返回有数据的,没有问题,又检查了WordsHandler,也没有问题。想了好久才意识到我在浏览器地址栏输入的中文会自动转码,而用HttpURLConnection访问时却不会自动转码。
所以在这里要手动的对中文进行重新编码。if里的isChinese()方法可以通过Unicode编码完美的判断中文汉字和符号

/*** 判断是否是中文** @param strName String类型的字符串*/public static boolean isChinese(String strName) {char[] ch = strName.toCharArray();for (int i = 0; i < ch.length; i++) {char c = ch[i];if (isChinese(c)) {return true;}}return false;}/*** 根据Unicode编码完美的判断中文汉字和符号** @param c char类型的字符串*/private static boolean isChinese(char c) {Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);if (ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION) {return true;}return false;}

到这里,基本的查词功能就能实现了!
其实我写这些代码的时候,会简单写一个Activity,里面有TextView,然后调用上述方法,测试我写的代码是否正确。

HttpUtil.sentHttpRequest(address, new HttpCallBackListener() {@Overridepublic void onFinish(InputStream inputStream) {WordsHandler wordsHandler = new WordsHandler();ParseXML.parse(wordsHandler, inputStream);words = wordsHandler.getWords();wordsAction.saveWords(words);wordsAction.saveWordsMP3(words);}@Overridepublic void onError() {}});}

这就是测试的时候简单调用方法,看看能不能实现功能。
今天就到这里,明天继续后续内容!

android 项目练习:自己的词典app——生词本(一)相关推荐

  1. android 项目练习:自己的词典app——生词本(二)

    附上guthub连接:https://github.com/huburt-Hu/VocabularyBuilder 继续接昨天的内容,没看过的可以点击 android 项目练习:自己的词典app--生 ...

  2. 新建android项目导包,Cordova开发App入门(一)创建android项目

    前言Apache Cordova是一个开源的移动开发框架.允许使用标准的web技术-HTML5,CSS3和JavaScript做跨平台开发. 应用在每个平台的具体执行被封装了起来,并依靠符合标准的AP ...

  3. Android项目笔记【项目管理统计图app】:使用github上的cardslib开源项目实现CardView(1)...

    因为项目中用到第三级菜单,我们原有的界面框架已经不适用于该项目,Android L出了新的cardview设计,爬了下github发现有些高手已经把card整合为更方便调用的类库了,我这个项目就准备试 ...

  4. 英文字典app android,学生英语词典app

    学生英语词典app,为用户带来便利的掌上词典功能,让你不再会遇到不认识的单词!程序驻留在通知栏,随时都可以快速查单词,快来试试吧 学生英语词典app介绍 这是一部内容丰富的英汉汉英双向词典,适合中学及 ...

  5. Eclipse新建Android项目后,出现“The import android.support.v7.app cannot be resolved”

    1>在Eclipse中新建Android项目后,出现"The import android.support.v7.app cannot be resolved" 如下图所示: ...

  6. Android项目实战(二十二):启动另一个APP or 重启本APP

    Android项目实战(二十二):启动另一个APP or 重启本APP 原文:Android项目实战(二十二):启动另一个APP or 重启本APP 一.启动另一个APP 目前公司项目需求,一个主AP ...

  7. 为什么Android项目mainactivity中有一个变量R_安卓4:第一个安卓程序 AS 安卓项目结构解析 手机运行app 模拟器运行app...

    学习于:https://www.bilibili.com/video/av22836860?p=2 首先,要知道AS的一个基本模型,1个Android project可以有多个module,而每个mo ...

  8. Android股票app模拟同花顺,适合练手的Android项目

    本项目是一款Android股票app软件,模拟同花顺,高仿微信九宫格图片浏览和Activity滑动返回,使用Volley网络请求和MVP框架 ,PullToRefreshRecyclerView.自定 ...

  9. idea跑android项目报A problem occurred configuring project ‘:app‘

    问题描述 本人idea2019.1.3升级到2019.3跑android项目时报A problem occurred configuring project ':app' 异常信息 A problem ...

最新文章

  1. 架构师之路 — 数据库设计 — 关系型数据库的约束类型
  2. 在linux命令下如何访问一个url?
  3. 如何查看ubuntu的版本信息和查看几位 32 or 64位
  4. java求最大值_java-求一组整数中的最大值
  5. 2018第一季度全球畅销手机排行出炉,苹果刷榜,小米三星各一款
  6. java io流的学习总结~~
  7. 全栈开发工程师微信小程序-上(中)
  8. lisp线段两端同时缩短的命令_老杨讲禅——线段与特征序列(1)
  9. 设置DIV最小高度以及高度自适应随着内容的变化而变化
  10. 【Matlab身份证识别】BP神经网络身份证号码识别【含源码 1344期】
  11. 关于navicat premium数据库激活一直不成功的解决方法(亲测有效)
  12. CDN是什么?CDN的工作原理?使用CDN有什么优势?
  13. STM32工作笔记0046---认识杜邦线_以及如何区分杜邦线公母_以及排线和杜邦线的区别
  14. A Pareto-Efficient Algorithm for Multiple Objective Optimization in E-Commerce Recommendation阅读翻译
  15. SpringBoot 2.X 整合 druid + dynamic-datasource 多数据源方案
  16. office2010安装过程出错(解决方法整合)
  17. 淘宝开放平台接口接口,订单R2权限接口淘宝开放平台R2权限,淘宝开放平台进存销应用,top平台接口,淘宝应用市场接口,ERP软件订单接口,淘上淘接口,
  18. 【历史上的今天】5 月 11 日:Dijkstra 算法开发者诞生;电子表格软件的开山鼻祖;机器狗 AIBO 问世
  19. 天猫精灵智能家居对接,及天猫iot官网配置图文讲解(一)
  20. uva10382 - Watering Grass

热门文章

  1. SAP ABAP openSQL数据库操作(四)
  2. 英语中并列句的四种类型
  3. 一文读懂寒武纪:AI芯片拓荒者的乘风破浪
  4. 团队管理那点破事!OKR绩效、核心人才、面试、技术分享、研发流程....
  5. dpdk内存管理分析
  6. MS coco数据集下载链接
  7. 微信隐藏功能,你们快来瞧一瞧
  8. 深信服“监控员工跳槽倾向”引争议,律师称未告知员工涉嫌违法
  9. 如何让PPT的备注演示者看到而观众看不到
  10. 北京博奥智源,发布ERP系统之财务管理及成本核算模块开发功能