附上guthub连接:https://github.com/huburt-Hu/VocabularyBuilder


继续接昨天的内容,没看过的可以点击 android 项目练习:自己的词典app——生词本(一) 查看。
昨天已经把查词界面的功能代码都完成了,今天就来完成UI界面的设计,由于本身不具备太多的艺术细胞,和所花时间有限,UI界面仅仅是凸显功能,并不美观。

查词界面UI:

xml布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/searchWords_fatherLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#E5E6E0"android:orientation="vertical"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="com.example.app.vocabularybuilder.activity.MainActivity"><SearchView
        android:id="@+id/searchWords_searchView"android:layout_width="match_parent"android:layout_height="wrap_content"android:queryHint="请输入要查询的单词" /><LinearLayout
        android:id="@+id/searchWords_linerLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:visibility="invisible"><RelativeLayout
            android:layout_width="match_parent"android:layout_height="60dp"><TextView
                android:id="@+id/searchWords_key"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentLeft="true"android:layout_alignParentStart="true"android:layout_alignParentTop="true"android:layout_marginLeft="20dp"android:layout_marginStart="20dp"android:text="abc"android:textSize="40dp" /></RelativeLayout><LinearLayout
            android:id="@+id/searchWords_posE_layout"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="20dp"><ImageButton
                android:id="@+id/searchWords_voiceE"android:layout_width="25dp"android:layout_height="25dp"android:background="@android:color/transparent"android:src="@drawable/voice" /><TextView
                android:id="@+id/searchWords_psE"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingTop="3dp"android:paddingLeft="5dp"android:paddingRight="5dp"android:paddingBottom="7dp"android:text="@string/psE"android:textColor="#3B3C3D"android:textSize="16dp" /></LinearLayout><LinearLayout
            android:id="@+id/searchWords_posA_layout"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="40dp"><ImageButton
                android:id="@+id/searchWords_voiceA"android:layout_width="25dp"android:layout_height="25dp"android:background="@android:color/transparent"android:src="@drawable/voice" /><TextView
                android:id="@+id/searchWords_psA"android:layout_width="wrap_content"android:layout_height="wrap_content"android:paddingTop="3dp"android:paddingLeft="5dp"android:paddingRight="5dp"android:paddingBottom="7dp"android:text="@string/psA"android:textColor="#3B3C3D"android:textSize="16dp" /></LinearLayout><LinearLayout
            android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"><LinearLayout
                android:layout_width="match_parent"android:layout_height="wrap_content"><ImageView
                    android:layout_width="30dp"android:layout_height="30dp"android:layout_gravity="center_vertical"android:src="@drawable/list" /><TextView
                    android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:padding="10dp"android:text="@string/posAcceptation"android:textSize="20dp" /></LinearLayout><TextView
                android:id="@+id/searchWords_posAcceptation"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="@drawable/layer_list_view"android:paddingBottom="5dp"android:paddingLeft="20dp"android:paddingRight="10dp"android:paddingTop="5dp"android:text="@string/pos"android:textSize="15dp" /><LinearLayout
                android:layout_width="match_parent"android:layout_height="wrap_content"><ImageView
                    android:layout_width="30dp"android:layout_height="30dp"android:layout_gravity="center_vertical"android:src="@drawable/list" /><TextView
                    android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:padding="10dp"android:text="@string/sent"android:textSize="20dp" /></LinearLayout></LinearLayout><ScrollView
            android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:background="@drawable/layer_list_view"><TextView
                android:id="@+id/searchWords_sent"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingBottom="5dp"android:paddingLeft="20dp"android:paddingRight="10dp"android:paddingTop="5dp"android:text="@string/pos"android:textSize="15dp" /></ScrollView></LinearLayout></LinearLayout>

应该很好理解,都是最常用的控件,searchView在Activity中还设置了两个属性:

searchView = (SearchView) findViewById(R.id.searchWords_searchView);
searchView.setSubmitButtonEnabled(true);//设置显示搜索按钮
searchView.setIconifiedByDefault(false);//设置不自动缩小为图标

然后看下效果图吧

xml布局文件的代码里可以看到最后一个例句的TextView外面包了一个ScrollView,因为上面其他控件的大小都是固定,只有这个TextView是补充剩余屏幕控件的,如果例句内容较多的话会出现超出屏幕,因此这里加了ScrolView保证能显示完整的例句内容。

还有一个细节问题,就是点击SearchView是会弹出软键盘,但是默认不会自动收回,所以我通过重写一个OnClickListener实现点击SwearchView以外的地方实现收回软键盘。

searchWords_fatherLayout = (LinearLayout) findViewById(R.id.searchWords_fatherLayout);
searchWords_fatherLayout.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//点击输入框外实现软键盘隐藏InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);}});

这里的searchWords_fatherLayout就是UI布局最外层的LinerLayout了。
这是我是参考了其他大神的方法: Android点击EditText文本框之外任何地方隐藏键盘的解决办法 。
还有一个细节点,当手机由竖直变为水平是,Activity也会跟着变换,这样会导致显示不全,因此我设置Activity永远是竖直状态的,方法是在Manifest.xml文件中添加:

<activity
            android:name=".activity.MainActivity"android:screenOrientation="portrait"> <!-- 固定竖屏 --><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity>

细心的朋友可能还看到我添加了ActionBar,那是打算做一些导航按钮和添加生词本的按钮,预先留出位置,目前还没有实现。
到此基本没有什么明显的瑕疵了,也实现显示查词结果的内容。
接下来就是发音MP3的储存和播放问题了。

查词发音

昨天我们访问查词接口的时候,还记得返回了这个http://res.iciba.com/resource/amp3/0/0/34/d1/34d1f91fb2e514b8576fab1a75a89a6b.mp3 ,这是MP3的地址,通过Http访问就可以得到对应的MP3文件,我们要做的就是把这个文件保存手机的SD卡中,并也有能够播放这个文件的方法。为什么是放在SD卡中?当然是为了保护手机的性能吗,这和在电脑上下载文件一般不放在C盘,而是放在D、E、F等盘中是一个道理。
现在util包下新建一个FileUtil类,用于封装对SD卡存储文件操作:

public class FileUtil {/*** SD卡的目录*/private String SDPath;/*** 本app存储的目录*/private String AppPath;/*** 本类的单例*/private static FileUtil fileUtil;/*** 私有化的构造器*/private FileUtil() {//如果手机已插入SD卡,且应用程序具有读写SD卡的功能,则返回trueif (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {SDPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";//清理测试时产生的文件
//            File f = new File(SDPath + "VocabularyBuilder");
//            deleteFile(f);File fileV = createSDDir(SDPath, "VocabularyBuilder");AppPath = fileV.getAbsolutePath() + "/";}}/*** 单例类FileUtil获取实例方法*/public static FileUtil getInstance() {if (fileUtil == null) {synchronized (FileUtil.class) {if (fileUtil == null) {fileUtil = new FileUtil();}}}return fileUtil;}/*** 创建目录** @param path    文件夹的路径* @param dirName 文件夹名*/public File createSDDir(String path, String dirName) {File dir = new File(path + dirName);if (dir.exists() && dir.isDirectory()) {return dir;}dir.mkdir();Log.d("测试", "创建目录成功");return dir;}/*** 创建SD文件** @param path     文件的路径* @param fileName 文件名*/public File createSDFile(String path, String fileName) {File file = new File(path + fileName);if (file.exists() && file.isFile()) {return file;}try {file.createNewFile();Log.d("测试", "创建文件成功");} catch (IOException e) {e.printStackTrace();}return file;}/*** 向SD卡中写入文件** @param path        文件夹名* @param fileName    文件名* @param inputStream 输入流*/public void writeToSD(String path, String fileName, InputStream inputStream) {OutputStream outputStream = null;try {File dir = createSDDir(AppPath, path);File file = createSDFile(dir.getAbsolutePath() + "/", fileName);outputStream = new FileOutputStream(file);int length;byte[] buffer = new byte[2 * 1024];while ((length = inputStream.read(buffer)) != -1) {//注意这里的length;//利用read返回的实际成功读取的字节数,将buffer写入文件,// 否则将会出现错误的字节,导致保存文件与源文件不一致outputStream.write(buffer, 0, length);}outputStream.flush();Log.d("测试", "写入成功");} catch (IOException e) {e.printStackTrace();Log.d("测试", "写入失败");} finally {try {if (outputStream != null) {outputStream.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 获取文件在SD卡上绝对路径,如无该文件返回""** @param fileName 单词对应的文件夹名*/public String getPathInSD(String fileName) {File file = new File(AppPath + fileName);if (file.exists()) {return file.getAbsolutePath();}return "";}
}

里面已经说得比较详细了,代码中”//注意这里的length;“这段内容最重要,一开始我是用:

outputStream.write(buffer);

这个方法写入的,但是测试的时候发现,音频会出现问题,查看文件大小会发现和源文件大小不一样。实际上这种方式写入会存在重复写入的问题,导致最终保存MP3比原文件要打,比方的时候会出现一小段重复,或者干脆不能播放的问题。
因此,这里要调用:outputStream.write(buffer, 0, length); 这个方法,此方法会利用read返回的实际成功读取的字节数,将buffer写入文件,避免重复写入同一个字符。

除了上面说到的,还有一个注意的点,这个问题同样困扰了我几个小时(⊙o⊙)…
毕竟是自己自学,常常会被一些小问题卡住好久 = _ =‘’

就总结说下,File创建一个文件如果是在很多层级下的话,一定要一层一层地创建,举个例子,我要创建一个文件的绝对路径是:E:/Directory/subDirectory/file.txt
但在创建之前”E:“下面没有”Directory“这个文件夹,如果这时候直接调用

File file = new File(E:/Directory/subDirectory/file.txt);
file.createNewFile();

是没有办法成功创建的,因此只能先创建”Directory“这个文件夹,再创建”subDirectory“这个文件夹,最后创建”file.txt“这个文件才行。

所以上面这个FileUtil类中的writeToSD()方法里,我是先创建文件夹,在创建文件的。

同样的,删除文件夹的时候,如果里面有文件,也是不能删除的,只有先把里面的文件删除光,才能删除文件夹。具体方法可以参考这个递归删除文件夹的方法:

/*** 递归删除文件夹** @param file 文件夹或者文件名*/private void deleteFile(File file) {if (file.exists()) {//判断文件是否存在if (file.isFile()) {//判断是否是文件file.delete();//删除文件} else if (file.isDirectory()) {//否则如果它是一个目录File[] files = file.listFiles();//声明目录下所有的文件 files[];for (int i = 0; i < files.length; i++) {//遍历目录下所有的文件deleteFile(files[i]);//把每个文件用这个方法进行迭代}file.delete();//删除文件夹}Log.d("测试", "删除成功");}}

完成了这个类,我们回到昨天创建的WordsAciton类中,添加这样两个方法:

/*** 保存words的发音MP3文件到SD卡* 先请求Http,成功后保存** @param words words实例*/public void saveWordsMP3(Words words) {String addressE = words.getPronE();String addressA = words.getPronA();if (addressE != "") {//判断有地址才发送网络请求final String filePathE = words.getKey();HttpUtil.sentHttpRequest(addressE, new HttpCallBackListener() {//请求完成后的回调方法@Overridepublic void onFinish(InputStream inputStream) {//调用FileUtil的方法将MP3文件保存到SD卡FileUtil.getInstance().writeToSD(filePathE, "E.mp3", inputStream);}@Overridepublic void onError() {}});}if (addressA != "") {final String filePathA = words.getKey();HttpUtil.sentHttpRequest(addressA, new HttpCallBackListener() {@Overridepublic void onFinish(InputStream inputStream) {FileUtil.getInstance().writeToSD(filePathA, "A.mp3", inputStream);}@Overridepublic void onError() {}});}}/*** 播放words的发音** @param wordsKey 单词的key* @param ps       E 代表英式发音*                 A 代表美式发音* @param context  上下文*/public void playMP3(String wordsKey, String ps, Context context) {String fileName = wordsKey + "/" + ps + ".mp3";String adrs = FileUtil.getInstance().getPathInSD(fileName);if (player != null) {if (player.isPlaying()) {player.stop();}player.release();player = null;}if (adrs != "") {//有内容则播放player = MediaPlayer.create(context, Uri.parse(adrs));Log.d("测试", "播放");player.start();} else {//没有内容则重新去下载Words words = getWordsFromSQLite(wordsKey);saveWordsMP3(words);}}

第一个方法就是网络请求,没什么问题。
第二个是meadiaPlayer播放MP3文件,加入了一些判断,如果没有找到音频文件的一些补救措施——再去下载一次。

最后,在Acitivity中调用这两个方法,完整的Activity代码如下:

public class MainActivity extends Activity {private SearchView searchView;private TextView searchWords_key, searchWords_psE, searchWords_psA, searchWords_posAcceptation, searchWords_sent;private ImageButton searchWords_voiceE, searchWords_voiceA;private LinearLayout searchWords_posA_layout,searchWords_posE_layout, searchWords_linerLayout, searchWords_fatherLayout;private WordsAction wordsAction;private Words words = new Words();/*** 网络查词完成后回调handleMessage方法*/private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 111://判断网络查找不到该词的情况if (words.getSent().length() > 0) {upDateView();} else {searchWords_linerLayout.setVisibility(View.GONE);Toast.makeText(MainActivity.this, "抱歉!找不到该词!", Toast.LENGTH_SHORT).show();}Log.d("测试", "tv保存2");}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);wordsAction = WordsAction.getInstance(this);//初始化控件searchWords_linerLayout = (LinearLayout) findViewById(R.id.searchWords_linerLayout);searchWords_posA_layout = (LinearLayout) findViewById(R.id.searchWords_posA_layout);searchWords_posE_layout = (LinearLayout) findViewById(R.id.searchWords_posE_layout);searchWords_fatherLayout = (LinearLayout) findViewById(R.id.searchWords_fatherLayout);searchWords_fatherLayout.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//点击输入框外实现软键盘隐藏InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);inputMethodManager.hideSoftInputFromWindow(v.getWindowToken(), 0);}});searchWords_key = (TextView) findViewById(R.id.searchWords_key);searchWords_psE = (TextView) findViewById(R.id.searchWords_psE);searchWords_psA = (TextView) findViewById(R.id.searchWords_psA);searchWords_posAcceptation = (TextView) findViewById(R.id.searchWords_posAcceptation);searchWords_sent = (TextView) findViewById(R.id.searchWords_sent);searchWords_voiceE = (ImageButton) findViewById(R.id.searchWords_voiceE);searchWords_voiceE.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {wordsAction.playMP3(words.getKey(), "E", MainActivity.this);}});searchWords_voiceA = (ImageButton) findViewById(R.id.searchWords_voiceA);searchWords_voiceA.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {wordsAction.playMP3(words.getKey(), "A", MainActivity.this);}});searchView = (SearchView) findViewById(R.id.searchWords_searchView);searchView.setSubmitButtonEnabled(true);//设置显示搜索按钮searchView.setIconifiedByDefault(false);//设置不自动缩小为图标searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {@Overridepublic boolean onQueryTextSubmit(String query) {loadWords(query);return true;}@Overridepublic boolean onQueryTextChange(String newText) {return false;}});}/*** 读取words的方法,优先从数据中搜索,没有在通过网络搜索*/public void loadWords(String key) {words = wordsAction.getWordsFromSQLite(key);if ("" == words.getKey()) {String address = wordsAction.getAddressForWords(key);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);handler.sendEmptyMessage(111);}@Overridepublic void onError() {}});} else {upDateView();}}/*** 更新UI显示*/public void upDateView() {if (words.getIsChinese()) {searchWords_posAcceptation.setText(words.getFy());searchWords_posA_layout.setVisibility(View.GONE);searchWords_posE_layout.setVisibility(View.GONE);} else {searchWords_posAcceptation.setText(words.getPosAcceptation());if(words.getPsE()!="") {searchWords_psE.setText(String.format(getResources().getString(R.string.psE), words.getPsE()));searchWords_posE_layout.setVisibility(View.VISIBLE);}else {searchWords_posE_layout.setVisibility(View.GONE);}if(words.getPsA()!="") {searchWords_psA.setText(String.format(getResources().getString(R.string.psA), words.getPsA()));searchWords_posA_layout.setVisibility(View.VISIBLE);}else {searchWords_posA_layout.setVisibility(View.GONE);}}searchWords_key.setText(words.getKey());searchWords_sent.setText(words.getSent());searchWords_linerLayout.setVisibility(View.VISIBLE);}//加载actionbar的菜单@Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.actionbar_layout_menu, menu);return super.onCreateOptionsMenu(menu);}
}

到这里整个查词的功能就完成了!谢谢大家的观看!

后续我完成了生词本的功能还会贴出来的!

这块功能的源码我上传了GitHub
有兴趣的可以下载看看:https://github.com/huburt-Hu/VocabularyBuilder.git

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

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

    前言: 自学android差不多两个月了,由于本身对英语不感冒,而且记英语单词总是很快忘记,因此学习的过程也是蛮累的,好多类和方法都不知道啥意思,还要去查词典才知道. 还是延续我读书时的记忆方法--每 ...

  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. android中获取某段程序的执行时间
  2. 约束理论学习随笔(2)---DBR系统
  3. HDU 1536 求解SG函数
  4. 漏洞挖掘、漏洞分析和漏洞利用
  5. staf工作笔记-stax自定义信号和信号处理功能
  6. Java 11新特性
  7. [Cubieboard] Node.js 在 Lubuntu 上安装指南
  8. Leetcode每日一题:1248.count-number-of-nice-subarrays(统计[优美子数组])
  9. checking for spandsp >= 3.0... configure: error: no usable spandsp; please install spandsp3 devel pa
  10. android 简介动画,android动画简介
  11. asp.net银行账目管理系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目
  12. 维纶触摸屏程序实际项目,威纶通界面UI
  13. python车牌识别_Python+Tensorflow+CNN实现车牌识别的示例代码
  14. 使用POI操作Excel的基本读写
  15. There is no getter for property named ‘pCode‘ in ‘classXXX‘
  16. 微型计算机开不了机,联想C225微型计算机怎么开不起机呢?
  17. dw常用标签_dreamweaver中常用到的标签及含义
  18. es拼音分词 大帅哥_SpringBoot集成Elasticsearch 进阶,实现中文、拼音分词,繁简体转换...
  19. 关于防火墙的调研报告
  20. 数理统计_正态分布经典例题

热门文章

  1. linux 识别磁带库,[转]红旗Linux下使用HP MSL 6060磁带库
  2. 智慧路灯合作伙伴生态大会(深圳站)
  3. KUDU(一)kudu概述
  4. python3 selenium 模拟登陆 获取cookies 保存到redis(安居客)
  5. 拉力测试软件界面,材料电子万能拉力试验机
  6. redis 发布订阅以及使用场景
  7. g-code 命令解释_2
  8. k8s中GPU虚拟化工具gpu-manager的安装
  9. 全网最全可视化大屏模板
  10. python小项目:MakeCode小游戏——躲砖块