android 项目练习:自己的词典app——生词本(二)
附上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——生词本(二)相关推荐
- android 项目练习:自己的词典app——生词本(一)
前言: 自学android差不多两个月了,由于本身对英语不感冒,而且记英语单词总是很快忘记,因此学习的过程也是蛮累的,好多类和方法都不知道啥意思,还要去查词典才知道. 还是延续我读书时的记忆方法--每 ...
- 新建android项目导包,Cordova开发App入门(一)创建android项目
前言Apache Cordova是一个开源的移动开发框架.允许使用标准的web技术-HTML5,CSS3和JavaScript做跨平台开发. 应用在每个平台的具体执行被封装了起来,并依靠符合标准的AP ...
- Android项目笔记【项目管理统计图app】:使用github上的cardslib开源项目实现CardView(1)...
因为项目中用到第三级菜单,我们原有的界面框架已经不适用于该项目,Android L出了新的cardview设计,爬了下github发现有些高手已经把card整合为更方便调用的类库了,我这个项目就准备试 ...
- 英文字典app android,学生英语词典app
学生英语词典app,为用户带来便利的掌上词典功能,让你不再会遇到不认识的单词!程序驻留在通知栏,随时都可以快速查单词,快来试试吧 学生英语词典app介绍 这是一部内容丰富的英汉汉英双向词典,适合中学及 ...
- Eclipse新建Android项目后,出现“The import android.support.v7.app cannot be resolved”
1>在Eclipse中新建Android项目后,出现"The import android.support.v7.app cannot be resolved" 如下图所示: ...
- Android项目实战(二十二):启动另一个APP or 重启本APP
Android项目实战(二十二):启动另一个APP or 重启本APP 原文:Android项目实战(二十二):启动另一个APP or 重启本APP 一.启动另一个APP 目前公司项目需求,一个主AP ...
- 为什么Android项目mainactivity中有一个变量R_安卓4:第一个安卓程序 AS 安卓项目结构解析 手机运行app 模拟器运行app...
学习于:https://www.bilibili.com/video/av22836860?p=2 首先,要知道AS的一个基本模型,1个Android project可以有多个module,而每个mo ...
- Android股票app模拟同花顺,适合练手的Android项目
本项目是一款Android股票app软件,模拟同花顺,高仿微信九宫格图片浏览和Activity滑动返回,使用Volley网络请求和MVP框架 ,PullToRefreshRecyclerView.自定 ...
- idea跑android项目报A problem occurred configuring project ‘:app‘
问题描述 本人idea2019.1.3升级到2019.3跑android项目时报A problem occurred configuring project ':app' 异常信息 A problem ...
最新文章
- android中获取某段程序的执行时间
- 约束理论学习随笔(2)---DBR系统
- HDU 1536 求解SG函数
- 漏洞挖掘、漏洞分析和漏洞利用
- staf工作笔记-stax自定义信号和信号处理功能
- Java 11新特性
- [Cubieboard] Node.js 在 Lubuntu 上安装指南
- Leetcode每日一题:1248.count-number-of-nice-subarrays(统计[优美子数组])
- checking for spandsp >= 3.0... configure: error: no usable spandsp; please install spandsp3 devel pa
- android 简介动画,android动画简介
- asp.net银行账目管理系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目
- 维纶触摸屏程序实际项目,威纶通界面UI
- python车牌识别_Python+Tensorflow+CNN实现车牌识别的示例代码
- 使用POI操作Excel的基本读写
- There is no getter for property named ‘pCode‘ in ‘classXXX‘
- 微型计算机开不了机,联想C225微型计算机怎么开不起机呢?
- dw常用标签_dreamweaver中常用到的标签及含义
- es拼音分词 大帅哥_SpringBoot集成Elasticsearch 进阶,实现中文、拼音分词,繁简体转换...
- 关于防火墙的调研报告
- 数理统计_正态分布经典例题