文章目录

  • 实训项目——多语言学习app 总结报告
    • 1. app 介绍
      • 1. 运行app
      • 2. 界面效果
    • 2. 开发过程
      • 1. 开发环境
      • 2. 项目结构
      • 3. 欢迎界面
      • 4. 用户与学习界面
      • 5. 语言选择界面
        • 将单词导入SQLite数据库
        • 从外部导入数据库到项目
      • 6. 单词界面
    • 3. 实训总结

实训项目——多语言学习app 总结报告

1. app 介绍

这是一款简单的学习语言的App,理论上可以选择多种语言进行学习,该App目前只实现了学习英语的功能。从功能上讲,它称为“背单词App"更为贴切。目前App拥有两千六百多个单词的词库,每个英语单词都附有词性和一个或多个中文意思。所有单词被均分在了36个unit中,用户每次可以选择其中一个unit进行学习。

1. 运行app

使用Android版本10及以上的手机下载APK安装包安装即可。

2. 界面效果

打开App,进入一个欢迎界面(界面1):

点击欢迎页面上的按钮,进入用户界面(界面2-1):

界面2-2

点击用户界面的 ”Login" 或者”返回语言选择“按钮,进入语言选择界面(理论上有四种语言可以选择,本App实现了学习英语的功能)(界面3):

点击底部导航栏的另一个按钮“学习”,进入学习界面(界面4):

学习界面上是单词的目录,可以通过滑动目录选择某一个unit的内容进行学习。点击其中一个unit,进入以下界面(界面5):

这是一个单词列表界面,每个英文单词附有词性和一个或多个中文释义。

2. 开发过程

1. 开发环境

操作系统:windows10
开发软件:Android Studio 3.5.2,中间经历了一次更新,更新到了4以上版本,影响不大

2. 项目结构

项目的代码结构:

res资源文件夹:

看起来代码结构和资源文件夹内容十分复杂,其中不然。

先来看看AndroidManifest.xml中注册的界面:

一共有四个Activity:MainActivity, MainPage, chooselanguage, showWord。这四个类是项目的主要部分,分别实现了App中的欢迎界面、用户与学习界面、语言选择界面、单词显示界面。因此接下来从它们入手分析。

3. 欢迎界面

对应的布局文件:activity_main.xml
即上述页面效果中的欢迎页面。

package com.example.wordsofmultilanguage;import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;public class MainActivity extends AppCompatActivity {private  Button startBn;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);startBn = (Button)findViewById(R.id.startBn);startBn.setOnClickListener(new click());}class click implements  View.OnClickListener{@Overridepublic void onClick(View v) {if(v == startBn){Intent intent = new Intent(MainActivity.this,MainPage.class);startActivity(intent);}}}
}

该Activity实现了onCreate()抽象方法,为页面上唯一一个按钮添加了监听事件setOnClickListener,当点击按钮时,开启另一个activity:MainPage

4. 用户与学习界面

点击欢迎界面的按钮后,展示的是以下界面的内容,这个界面分为三个部分:

各个部分的联系是这样的:当点击用户按钮时,显示的标题是”个人档案“,中间是用户的信息,当点击学习按钮时,标题栏变成了“学习英语”,中间部分的内容变成了单词目录(即上述的界面4)。标题栏样式简单,只有文本的改变,可以在切换界面时通过setTitle()方法实现;中间部分比较复杂,是通过控件 fragment 实现的。’

Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑, 当然现在他仍然是平板APP UI设计的宠儿,而且我们普通手机开发也会加入这个Fragment, 我们可以把他看成一个小型的Activity,又称Activity片段!想想,如果一个很大的界面,我们 就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦!而使用Fragment 我们可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理!从而可以更加方便的在 运行过程中动态地更新Activity的用户界面!另外Fragment并不能单独使用,他需要嵌套在Activity 中使用,尽管他拥有自己的生命周期,但是还是会受到宿主Activity的生命周期的影响,比如Activity 被destory销毁了,他也会跟着销毁!

简单来说,如果想要通过点击导航栏不同按钮切换中间部分的内容,就需要把中间部分分别放入一个fragment中,点击哪个部分就显示哪个部分的内容。于是问题转变成了如何将中间部分放入fragment中。

首先需要创建一个fragment。fragment有静态加载和动态加载两种:
静态加载:

动态加载:

这里使用的是静态加载。
首先,第一步,定义fragment的布局,就是fragment 显示内容的。
用户部分user.xml:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="wrap_content"android:layout_height="match_parent"><androidx.constraintlayout.widget.ConstraintLayoutandroid:id="@+id/userpage"android:layout_width="match_parent"android:layout_height="645dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"><Buttonandroid:id="@+id/login"android:layout_width="125dp"android:layout_height="125dp"android:layout_marginStart="143dp"android:layout_marginLeft="143dp"android:layout_marginTop="125dp"android:layout_marginEnd="143dp"android:layout_marginRight="143dp"android:background="@drawable/btn"android:text="@string/login"android:textAllCaps="false"android:textColor="@android:color/background_light"android:textSize="30sp"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/infoAndSettings"app:layout_constraintTop_toTopOf="parent"tools:layout_conversion_absoluteHeight="0dp"tools:layout_conversion_absoluteWidth="0dp" /><Buttonandroid:id="@+id/userinfor"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="75dp"android:width="235dp"android:background="#87CEEB"android:text="@string/user_information"android:textColor="@android:color/white"app:layout_constraintEnd_toStartOf="@+id/userset"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toBottomOf="@+id/login"tools:layout_conversion_absoluteHeight="0dp"tools:layout_conversion_absoluteWidth="0dp" /><Buttonandroid:id="@+id/userset"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="75dp"android:width="235dp"android:background="@drawable/button_selector"android:text="@string/user_settings"android:textSize="14sp"android:textColor="#87CEEB"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toEndOf="@+id/userinfor"app:layout_constraintTop_toBottomOf="@+id/login"tools:layout_conversion_absoluteHeight="0dp"tools:layout_conversion_absoluteWidth="0dp" /><FrameLayoutandroid:id="@+id/infoAndSettings"android:layout_width="405dp"android:layout_height="150dp"android:layout_marginTop="380dp"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="1.0"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.157" /></androidx.constraintlayout.widget.ConstraintLayout></androidx.constraintlayout.widget.ConstraintLayout>

布局效果如图所示(最大的蓝框部分):

至于为什么用户信息/用户设置按钮底下的部分本来应该有内容,现在却是空白的,这又是另一个故事了。因为这部分其实也使用了fragment,使得点击用户信息和用户设置按钮时显示不同的内容,所以这里有嵌套fragment的使用,之后再讨论。

目录部分study.xml:

<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="645dp"><androidx.appcompat.widget.LinearLayoutCompatandroid:layout_width="match_parent"android:layout_height="525dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"><ListViewandroid:id="@+id/catalog_info"android:layout_width="match_parent"android:layout_height="wrap_content" /></androidx.appcompat.widget.LinearLayoutCompat>
</androidx.constraintlayout.widget.ConstraintLayout>

目录部分只有一个控件:ListvView。
布局效果如图所示(蓝框部分):

这里目录的实现使用了适配器Adapter,依靠代码实现,所以从布局上来讲这部分也暂时没有内容,之后再讨论。

完成了第一步之后,接着,第二步,分别为user和study定义一个fragment类。
user.java继承了Fragment, 重写onCreateView()方法,在该方法中调用inflater.inflate()方法加载Fragment的布局文件,接着返回加载的view对象。study.java也同理。

    @Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.user, container, false);return view;}

来到第三步,在需要加载Fragment的Activity对应的布局文件中添加frameLayout的标签。该布局文件就是包含导航栏在内的整体布局。
page_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="wrap_content"android:layout_height="wrap_content"><FrameLayoutandroid:id="@+id/studyAndUser"android:layout_width="405dp"android:layout_height="wrap_content"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.0"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"></FrameLayout><!-- 导航栏的布局--><LinearLayoutandroid:id="@+id/navigation"android:layout_width="401dp"android:layout_height="75dp"android:layout_marginTop="525dp"android:orientation="horizontal"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"><RadioGroupandroid:id="@+id/rg_main"android:layout_width="0dp"android:layout_height="75dp"android:orientation="horizontal"android:layout_gravity="center"android:layout_weight="1"android:gravity="center" ><RadioButtonandroid:id="@+id/studypagebtn"android:button="@null"android:layout_width="150dp"android:layout_height="75dp"android:layout_gravity="center"android:layout_weight="1"android:drawableTop="@drawable/tab_study_channel"android:gravity="center"android:text="@string/tab_study_button"android:textSize="18sp" /><RadioButtonandroid:id="@+id/userpagebtn"android:layout_width="150dp"android:layout_height="match_parent"android:layout_gravity="center"android:layout_weight="1"android:button="@null"android:checked="true"android:drawableTop="@drawable/tab_user_channel"android:gravity="center"android:text="@string/tab_user_button"android:textSize="18sp" /></RadioGroup></LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

导航栏的两个图标是用RadioGroup组件实现的。

第四步, Activity中,完成上述两个fragment的初始化和替换规则。在onCreate( )方法中调用setContentView()加载布局文件,初始时显示的是user的fragment,并给导航栏两个按钮增加监听事件,当点击用户按钮时显示user的fragment,并隐藏另一个fragment;反之亦然。
MainPage.java:

protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.page_main);this.setTitle("个人档案");// this.setActionBar();studypagebtn = (RadioButton) findViewById(R.id.studypagebtn);studypagebtn.setClickable(true);studypagebtn.setOnClickListener(new onclick());userpagebtn = (RadioButton) findViewById(R.id.userpagebtn);userpagebtn.setClickable(true);userpagebtn.setOnClickListener(new onclick());TextPaint tp = userpagebtn.getPaint();tp.setFakeBoldText(true);studyAndUser = (FrameLayout)findViewById(R.id.studyAndUser);fragmentManager = getSupportFragmentManager();initUserFragment();}private void initUserFragment(){FragmentTransaction transaction = fragmentManager.beginTransaction();if(u == null){u = new user();transaction.add(R.id.studyAndUser,u);}hideFragment(transaction);transaction.show(u);transaction.commit();}private void initStudyFragment(){FragmentTransaction transaction = fragmentManager.beginTransaction();if(s == null){s = new study();transaction.add(R.id.studyAndUser,s);}hideFragment(transaction);transaction.show(s);transaction.commit();}private void hideFragment(FragmentTransaction transaction){if(u != null){transaction.hide(u);}if(s != null){transaction.hide(s);}}class onclick implements View.OnClickListener{@Overridepublic void onClick(View v) {if(v.getId() == R.id.userpagebtn){setTitle("个人档案");TextPaint tp = userpagebtn.getPaint();tp.setFakeBoldText(true);TextPaint tp1 = studypagebtn.getPaint();tp1.setFakeBoldText(false);initUserFragment();}else if(v.getId() == R.id.studypagebtn){setTitle("学习英语");TextPaint tp = studypagebtn.getPaint();tp.setFakeBoldText(true);TextPaint tp1 = userpagebtn.getPaint();tp1.setFakeBoldText(false);initStudyFragment();}}}
}

现在,我们要的效果已经完成了。user和study之间可以随意切换了。

但是,实际上user和study目前都是不完整的,因为我们还没解决嵌套fragment和Adapter的问题。接下来分别分析这两个部分。

本身user就是一个fragment,在它的内部,还有另一个问题,就是实现点击“用户信息”和“用户设置”按钮时在同一界面内显示不同的内容,通过以上内容的分析可以知道,把这部分不同的内容分别放进fragment可以实现这个效果。
因此,重复上述的四步,这个问题就解决了。

略有不同的是,在第四步中,两个fragment的初始化和替换规则并不是在Activity类中实现的。由于它是user这个fragment中的一部分,跟随user出现和隐藏,因此在user.java中的OnCreateView()方法中:

private Button userInfo;
private Button userSettings;
private userInforFragment userinforfragment;
private userSettingsFragment usersettingfragment;
private FrameLayout infoandsettings;
private FragmentManager fragmentManager;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.user, container, false);userInfo = (Button)view.findViewById(R.id.userinfor); //用户信息按钮userInfo.setClickable(true);userInfo.setOnClickListener(new onclick()); //为用户信息按钮增加监听事件userSettings = (Button)view.findViewById(R.id.userset); //用户设置按钮userSettings.setOnClickListener(new onclick()); // 为用户设置按钮增加监听事件infoandsettings = (FrameLayout)view.findViewById(R.id.infoAndSettings); //第三步中添加的FrameLayout的标签fragmentManager = getChildFragmentManager();initUserInforFagment();  // 初始时显示用户信息按钮对应的内容return view;}

初始化和替换规则:

 private void initUserInforFagment(){FragmentTransaction transaction = fragmentManager.beginTransaction();if(userinforfragment == null){userinforfragment = new userInforFragment();transaction.add(R.id.infoAndSettings,userinforfragment);}hideFragment(transaction);transaction.show(userinforfragment);transaction.commit();}private void initUserSettingsFragment(){FragmentTransaction transaction = fragmentManager.beginTransaction();if(usersettingfragment == null){usersettingfragment = new userSettingsFragment();transaction.add(R.id.infoAndSettings, usersettingfragment);}hideFragment(transaction);transaction.show(usersettingfragment);transaction.commit();}private void hideFragment(FragmentTransaction transaction){if(userinforfragment != null){transaction.hide(userinforfragment);}if(usersettingfragment != null){transaction.hide(usersettingfragment);}}class onclick implements View.OnClickListener{@Overridepublic void onClick(View v) {if(v == userInfo){userSettings.setBackgroundColor(Color.WHITE);userSettings.setTextColor(Color.rgb(135,206,235));v.setBackgroundColor(Color.rgb(135,206,235));userInfo.setTextColor(Color.WHITE);initUserInforFagment();}else if(v == userSettings){userInfo.setBackgroundColor(Color.WHITE);userInfo.setTextColor(Color.rgb(135,206,235));v.setBackgroundColor(Color.rgb(135,206,235));userSettings.setTextColor(Color.WHITE);initUserSettingsFragment();}}}

嵌套fragment的部分也完成了。

现在该考虑如何在study中显示出目录了。目录其实就是一个列表,在Android开发中,列表的实现最先想到的就是用ListView了,在study.xml中唯一一个控件就是它。ListView通常搭配Adapter使用,AS中提供了几种类型的Adapter:

这里使用的是第一种,继承BaseAdapter类然后重写相关方法。
首先我们定义一个Catalog类:

package com.example.wordsofmultilanguage;public class Catalog {private String catalog_name;public Catalog(){}public Catalog(String name){this.catalog_name = name;}public String getCatalog_name() {return catalog_name;}public void setCatalog_name(String catalog_name) {this.catalog_name = catalog_name;}
}

接着定义一个继承BaseAdapter的CatalogAdapter类:

public class CatalogAdapter extends BaseAdapter {private LinkedList<Catalog>mData;private Context mcontext;public CatalogAdapter(LinkedList<Catalog>mData, Context mcontext){this.mData = mData;this.mcontext = mcontext;}@Overridepublic int getCount(){return mData.size();}@Overridepublic Object getItem(int position){return null;}@Overridepublic long getItemId(int position){return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent){convertView = LayoutInflater.from(mcontext).inflate(R.layout.catalog_item,parent,false);TextView catalog_name = (TextView)convertView.findViewById(R.id.catalog_name);catalog_name.setText(mData.get(position).getCatalog_name());return convertView;}
}

最后,在study.java中的onCreateView()方法,向Adapter添加列表的每个item(这里用了很笨的一种方法,就是逐个add,菜鸟哭泣,其实可以优化一下)

 private List<Catalog> mData = null;private Context mcontext;private CatalogAdapter cAdapter = null;private ListView catalog_info;public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.study, container, false);//init();mcontext = view.getContext();catalog_info = (ListView)view.findViewById(R.id.catalog_info);mData = new LinkedList<Catalog>();mData.add(new Catalog("TOUR 1"));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 2"));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 3 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 4 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 5 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 6 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 7 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 8 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));mData.add(new Catalog("TOUR 9 "));mData.add(new Catalog("unit 1"));mData.add(new Catalog("unit 2"));mData.add(new Catalog("unit 3"));mData.add(new Catalog("unit 4"));cAdapter = new CatalogAdapter((LinkedList<Catalog>)mData, mcontext){public View getView(int position, View convertView, ViewGroup parent){View view = super.getView(position, convertView, parent);if(position % 5 != 0){view.setBackgroundColor(Color.WHITE);}return view;}};catalog_info.setAdapter(cAdapter);

该界面至此才算真正实现了想要的效果。

5. 语言选择界面

这个界面对应的是chooselanguage这一activity,它通过点击用户界面的“Login"或者”返回语言选择“按钮来触发,布局文件也很简单,主要是ImageView和TextView控件的使用。它的难点在于,选择语言之后,要如何加载对应的语言库(以英语为例,选择了英语之后就要加载出英文单词库)?

这需要解决三个问题:

  1. 单词库从何而来?
  2. 存储在哪里?
  3. 怎么显示出来?

假设我们已经有了单词库,不考虑第1个问题,也依然需要解决后两个问题。

通过进一步的学习了解到,Android的数据存储与访问有几种方式,其中一种就是SQLite数据库。

SQLite支持五种数据类型:NULL,INTEGER,REAL(浮点数),TEXT(字符串文本)和BLOB(二进制对象) 虽然只有五种,但是对于varchar,char等其他数据类型都是可以保存的;因为SQLite有个最大的特点: 你可以各种数据类型的数据保存到任何字段中而不用关心字段声明的数据类型是什么,比如你 可以在Integer类型的字段中存放字符串,当然除了声明为主键INTEGER PRIMARY KEY的字段只能够存储64位整数! 另外, SQLite 在解析CREATE TABLE 语句时, 会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息如下面语句会忽略 name字段的类型信息: CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))

小结下特点:
SQlite通过文件来保存数据库,一个文件就是一个数据库,数据库中又包含多个表格,表格里又有 多条记录,每个记录由多个字段构成,每个字段有对应的值,每个值我们可以指定类型,也可以不指定 类型(主键除外)

结合我们的需求分析,单词库中一个单词就是一条记录,一个单词由多部分构成:英文、词性和中文,我们要存储的就是每条单词记录。而SQLite数据库很好地符合了我们的需求,因此我们接下来要做的,就是将单词导入SQLite数据库,然后将数据库导入到项目中。

将单词导入SQLite数据库

能够导入数据库的数据,必须是db格式的。
通常来说,单词库一般是txt或者json格式,也可以直接找db格式的,那以下步骤就省了。
如果不是db格式的单词库,那就需要按以下步骤逐步执行。

  1. 下载一个有严格格式的词汇表;可以是txt也可以是doc(doc可以转化为txt),但内容的分隔符一定要明确且统一;
  2. 打开excel软件,wps也可以,新建一个excel表格,设置好表头,然后选择:数据-导入数据-直接打开数据文件,此时选择下载好的txt文件;
  3. 接着设置utf-8编码(因为该编码支持中文),再按照txt文件中使用的分隔符来设置分隔符,一般是tab键或者空格,也可以是自定义内容,设置完成之后开始导入;
  4. 导入完成之后检查一遍(很有必要),没有问题的话将文件保存为csv格式(csv默认以逗号分隔符保存表格),将编码格式设置为utf-8(避免之后导入SQLite乱码)。
  5. 打开SQLite Expert Professional软件(这个软件有personal版和pro版,前者免费后者免费试用几十天,但我用personal版时似乎不支持该步骤),新建一个数据库,选择import text file, 将之前保存的csv格式的文件导入,分隔符设置为comma。

完成以上步骤后,不出意外的话一个单词数据库就完成了。

完成导入之后,还需要将该数据库(我命名为1.db)从外部导入项目中。

从外部导入数据库到项目

将项目切换至Project视图,新建一个和Java、res同级的目录assets(这个目录的位置与开发环境有关,我看网上说使用Eclispe开发环境时它在res/下,但在AS中它必须与res目录同级),然后将1.db复制到assets目录下:

至于为什么要将数据库文件放在Assets下:

有个解释说,这个文件夹主要用于存放应用程序中使用的外部资源文件,然后程序可以通过 I/O 流(使用AssetManager)对目录中的文件进行读写,存放在此目录下的文件都会被打包到发布包中。

接着,实现类MyDBOpenHelper.java;

这个类就是实现从assets目录读取数据库文件然后写入SD卡中,如果在SD卡中存在,就打开数据库,不存在就从assets目录下复制过去。

import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.database.Cursor;
import android.util.Log;public class MyDBOpenHelper extends SQLiteOpenHelper {private static String PACKAGE_NAME = "com.example.wordsofmultilanguage"; //包名private static String DB_PATH =  "/data" + Environment.getDataDirectory().getAbsolutePath() + "/" + PACKAGE_NAME + "/databases/";private static String DB_NAME = "1";private SQLiteDatabase db;private final Context context;public MyDBOpenHelper(Context context) {super(context,  DB_NAME , null, 1);this. context  = context;}public void createDB() throws IOException {this.getReadableDatabase();try {copyDB();} catch (IOException e) {throw new Error("Error copying database");}}public void copyDB() throws IOException{try {InputStream ip =  context.getAssets().open(DB_NAME+".db");Log.i("Input Stream....",ip+"");String op=  DB_PATH  +  DB_NAME ;OutputStream output = new FileOutputStream( op);byte[] buffer = new byte[1024];int length;while ((length = ip.read(buffer))>0){output.write(buffer, 0, length);Log.i("Content.... ",length+"");}output.flush();output.close();ip.close();}catch (IOException e) {Log.v("error", e.toString());}}public void openDB() throws SQLException {String myPath = DB_PATH + DB_NAME;db = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);Log.i("open DB......",db.toString());}@Overridepublic synchronized void close() {if(db != null)db.close();super.close();}@Overridepublic void onCreate(SQLiteDatabase db) {}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}

完成第一步外部数据库的导入之后,定义一个类initdate.java用来实例化数据库:

public class initdate {public String word;public String meaning1;public String meaning2;public String meaning3;public initdate(String word, String meaning1, String meaning2, String meaning3){this.word = word;this.meaning1 = meaning1;this.meaning2 = meaning2;this.meaning3 = meaning3;}
}

然后,打开数据库文件,并且用一个initdate类型的ArrayList存储起来:

  List<initdate>list = new ArrayList<>();MyDBOpenHelper db;db = new MyDBOpenHelper(this);try {db.createDB();} catch (IOException ioe) {throw new Error("Database not created....");}try {db.openDB();} catch (SQLException sqle) {throw sqle;}SQLiteDatabase db1;db1 = openOrCreateDatabase("1", Context.MODE_PRIVATE, null);Cursor c = db1.rawQuery("SELECT * FROM words1", null);c.moveToFirst();//获取表数据while (!c.isAfterLast()) {list.add(new initdate(c.getString(c.getColumnIndex("word")),c.getString(c.getColumnIndex("meaning1")),c.getString(c.getColumnIndex("meaning2")),c.getString(c.getColumnIndex("meaning3"))));c.moveToNext();}

现在,我们已经将单词库数据读入到了项目中,并且储存到了list中。

6. 单词界面

我们想要达到这样的效果:点击目录的任何一个unit,都能显示出一个单词列表,这又要如何实现呢?
由于已经有了存储单词信息的list,因此这一步的实现并不太难。
单词列表与目录一样,可以通过ListView控件来实现。
show_word.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><ListViewandroid:id="@+id/fword_info"android:layout_width="match_parent"android:layout_height="wrap_content" />
</LinearLayout>

每一个单词就是ListView的 item,item的布局show_word_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/fword"android:paddingLeft="10dp"android:textSize="24sp"/><TextViewandroid:id="@+id/fmean1"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:textSize="16sp" /><TextViewandroid:id="@+id/fmean2"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:textSize="16sp" /><TextViewandroid:id="@+id/fmean3"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dp"android:textSize="16sp" /></LinearLayout>

目录中的unit其实也是study.xml的ListView控件中的一个item,可以对ListView增加监听事件,使得点击任意的item,都能对此做出反应:

// catalog_info:ListView的 idcatalog_info.setOnItemClickListener(new AdapterView.OnItemClickListener(){@Overridepublic void onItemClick(AdapterView<?>parent, View v, int position, long id){if(position % 5 == 0){}else {Intent intent = new Intent();intent.setClass(getContext(), showWord.class);Bundle bundle = new Bundle();bundle.putInt("position", position);intent.putExtras(bundle);startActivity(intent);}}});

上述代码的功能是,点击item时,将item在ListView中的位置通过Bundle来传递给另一个Activity,并且开启该Activity。这里开启的Activity就是showWord。至于为什么要传递item的位置,我的想法是每个item(unit)对应的单词是固定的,就跟英语课本上指定了unit就相当于知道了都有哪些单词,将unit与单词进行绑定,因此需要通过位置来辨别是unit。

然后,在showWord.java中,接收Bundle传来的数据,通过计算显示list中某一部分的内容。这里也用到了Adapter。

public class showWord extends AppCompatActivity {List<initdate>list1 = new ArrayList<>();@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.show_word);ListView listView = (ListView)this.findViewById(R.id.fword_info);Bundle bundle = this.getIntent().getExtras();int pos = bundle.getInt("position");MyDBOpenHelper db;db = new MyDBOpenHelper(this);try {db.createDB();} catch (IOException ioe) {throw new Error("Database not created....");}try {db.openDB();} catch (SQLException sqle) {throw sqle;}SQLiteDatabase db1;db1 = openOrCreateDatabase("1", Context.MODE_PRIVATE, null);Cursor c = db1.rawQuery("SELECT * FROM words1", null);c.moveToFirst();//String temp = "";while (!c.isAfterLast()) {list1.add(new initdate(c.getString(c.getColumnIndex("word")),c.getString(c.getColumnIndex("meaning1")),c.getString(c.getColumnIndex("meaning2")),c.getString(c.getColumnIndex("meaning3"))));c.moveToNext();}List<HashMap<String, Object>> data1 = new ArrayList<HashMap<String, Object>>();int count = pos / 5 + 1;for(int i = (pos - count) * 74; i < (pos - count + 1) * 74 && i < list1.size(); i++){HashMap<String, Object>item = new HashMap<String, Object>();item.put("word", list1.get(i).word);item.put("meaning1", list1.get(i).meaning1);if(list1.get(i).meaning2 != null){item.put("meaning2", list1.get(i).meaning2);}//item.put("meaning2", list.get(i).meaning2);if(list1.get(i).meaning3 != null){item.put("meaning3", list1.get(i).meaning3);}//item.put("meaning3", list.get(i).mmeaning3);data1.add(item);}SimpleAdapter adapter = new SimpleAdapter(this, data1, R.layout.show_word_item,new String[]{"word", "meaning1", "meaning2", "meaning3"}, new int[]{R.id.fword, R.id.fmean1, R.id.fmean2, R.id.fmean3});listView.setAdapter(adapter);}
}

最后的效果也实现了。
项目到这里实现得差不多了,很多细节的内容,实现起来比较简单的,这里就不详细展示了。

3. 实训总结

不知道这算不算一个真正意义上的App,毕竟真的太粗糙了。但这确实是第一个自己接触的基于Android开发的项目,由于之前没有任何基础,所有内容都是从零学起,从最开始什么都不懂到慢慢勉强可以上手,我还记得第一次写周报的时候我的问题是怎么跳转页面……比起那时可能有了一点点进步吧,别的不说,起码动手查资料的能力增强了不少哈哈哈。现在掌握的内容也不多,从这个开发过程就可以看出来涉及的更多的是基础的UI设计,Fragment,ListView,Button这些基本控件的使用,还有Adapter,Bundle,SQLite的利用等。整个开发周期断断续续经历了一个学期,真正花在上面的时间不算多,平均下来一周一天左右。一方面是因为这学期课比较多作业多,另一方面是自己总会遇到各种奇奇怪怪的问题(真是菜鸟弯路多),大部分时间又用来解决问题了。

其实之前我对App的构想不止如此。我所做的是Android开发的案例1,这个案例的要求是实现基本的UI界面设计,但无知者无畏,我当时还说想要把它做成真正有学习功能的App,结果快期末了都没有完成。。一些想要的效果还没有实现就要展示出来,这大概是一种遗憾吧。

要说收获,最重要的是培养了我对Android开发的兴趣。获取新的理论知识,到把这个知识转化为实践,然后慢慢解决中间出现的各种问题,这个过程还有很有成就感的。学无止境,如果可以,希望放假能继续完善这个App的各项功能,也算给自己一个交代。

最后,感谢老师和助教一个学期的辅导,谢谢你们!

实训项目——多语言学习app 总结报告相关推荐

  1. C语言循环语句实训项目,C语言程序设计与项目实训

    C语言程序设计与项目实训 编辑 锁定 讨论 上传视频 <C语言程序设计与项目实训>是2013年清华大学出版社出版的图书,作者是吴艳平.徐海燕. 书    名 C语言程序设计与项目实训 作  ...

  2. 实训项目计算机基础知识实训报告,计算机应用基础(本)形考作业实训2学习实训报告.docx...

    <计算机应用基础(本)形考作业实训2学习实训报告.docx>由会员分享,提供在线免费全文阅读可下载,此文档格式为docx,更多相关<计算机应用基础(本)形考作业实训2学习实训报告.d ...

  3. C语言实训 实训项目一 统计歌唱比赛成绩

    实训项目一 统计歌唱比赛成绩 有10名选手参加某歌唱比赛,每位选手演唱自己参赛曲目后,7名评委亮出自己的评分,后台人员会去掉最高分和最低分,将剩下的5个值求平均来计算该选手的比赛得分.比赛结束后,将根 ...

  4. 国开电大 公共关系学 形考任务答案 实训项目

    形考任务1 题目1公共关系是一门综合性的应用科学. 正确答案是:对 题目2公共关系特指组织与公众之间的传播沟通关系. 正确答案是:对 题目3美国学者香农提出了双向循环的传播模式. 正确答案是:错 题目 ...

  5. 实训小结---Java框架学习心得

    学习小结 为期两个星期的实训结束了,在这两周的时间里,我学到了不少之前没有接触过的知识,这也是我第一次正式接触前端后端的开发.在学习开发的过程中,出现过不少问题,自己能够解决的.自己不能解决的都有,下 ...

  6. python实训项目-黑马程序员上海校区Python21期Django项目实训

    黑马程序员上海中心 月薪一万只是起点 关注 晚上十点,一名名Python学生正在酣畅淋漓地撸代码,手指不间断地敲击着键盘,发出机械而清脆的声音. 各个小组在经过为期4天的django项目小组开发,终于 ...

  7. 安卓实训项目:基于储存卡音乐播放器实训报告5.0

    安卓实训项目:基于储存卡音乐播放器实训报告5.0 (一)功能要求 在基于存储卡音乐播放器V0.4基础上,作如下修改: 1.创建应用程序常量接口保存广播频道常量 2.在音乐播放器应用程序类里添加两个属性 ...

  8. 基于tutk方案的p2p源码_基于JAVA的局域网文件共享平台P2P实训项目源码(毕业设计 课程设计)...

    [实例简介] 基于JAVA的局域网文件共享平台P2P实训项目源码(毕业设计 课程设计). 可直接运行.做毕业设计.课程设计或者想研究下技术的可以下载学习.需要更多资源的可以关注我. [实例截图] [核 ...

  9. 常见的预设分栏包括_计算机应用基础_实训项目二Word综合应用

    . 专业学习资料 . 实训项目二 Word 综合应用示例 实训项目二 Word 综合应用 实训满分 20 分 . 以日常应用为基础 , 设计一个综合运用 Word 基本操作技能解决实际问题的文档 . ...

最新文章

  1. MySQL复制经常使用拓扑结构具体解释
  2. python中的_init_的含义
  3. nfs文件服务器以及客户端基本配置
  4. javaweb学习6——自定义标签
  5. [浏览器]Apple之Safari 5.0.4
  6. 怎么删除python工程_python根据字典的键来删除元素的方法
  7. Vmware16一打开虚拟机就蓝屏
  8. 即时通讯学习笔记001---XMPP了解认知
  9. 实验2-2-1 计算分段函数[1] (10 分)
  10. 顺序表的十个基本操作(全)
  11. rom大小 stm32f205_最小的基于STM32F205VE的系统板
  12. 数据仓库中元数据的定义、用途及元数据的分类。
  13. 后台实现查找附近的人接口
  14. 描述 由于被认为是客户端对错误(例如:畸形的请求语法、无效的请求信息帧或者虚拟的请求路由),服务器无法或不会处理当前请求。
  15. 详解网关和路由器的区别
  16. 新手小白如何用linux云服务器搭建wordpress个人网站
  17. ESD和TVS管的区别
  18. C# 调用DXGI采集屏幕
  19. C++ 图片完整性校验
  20. 求解TSP的改进模拟退火算法研究

热门文章

  1. 信号的概念,认识,处理动作
  2. 大话Linux之 跟我走吧,现在就出发[2]Linux我想和你在一起(2)Vim,vim,I love you!有图有真相!
  3. SDRAM学习(一)——初始化
  4. 解决优盘插入电脑后无显示的问题
  5. HDU 1422 重温世界杯 【C++练习题】
  6. linux配置命令的各列解释,很实用的linux运维常用命令及知识 | 旺旺知识库
  7. Windows设置/去除C盘的写保护
  8. 以EV录屏为例详细讲解-录屏,开直播的全局配置
  9. java连接neo4j(使用spring data neo4j)
  10. 读完 Effective Java,我整理了 59 条技巧!(含pdf)