项目三 开发手机通讯录

本项目工作情景的目标是让学生掌握Android的数据管理技术。主要的工作任务划分为
① 添加联系人记录 ② 修改联系人记录
③ 查找号码记录
④ 查看联系人记录
⑤ 删除号码记录
⑥ 对外共享数据
⑦ 设计主界面。
本项目主要涉及的关键技术包括:SQLite数据库的使用、Activity类的切换、ListView和Adapter结合显示数据、ContentProvider共享数据等。

3.1 Android的数据存储技术

无论使用何种平台或开发环境,也不管开发哪种类型的应用程序,数据处理都是核心。一个对数据存储有良好支持的开发平台将会对应用程序的开发发挥良好的促进作用。
应用程序的数据存储方式主要分为3类:文件存储、数据库存储和网络存储。其中文件存储可以自行定义数据格式,使用较为灵活;数据库存储在管理大量数据时较为方便,性能较高,不仅能够对数据进行查询、删除、增加、修改操作,还可以进行加密、加锁、跨应用和跨平台等操作;网络存储则用于实时数据的处理,如在运输、科研、勘探、航空、移动办公等场景下,实时将采集到的数据通过网络传输到数据处理中心。
对Android平台而言,其存储方式同样是包括文件存储、数据库存储和网络存储3种。但从开发者具体使用的角度来划分,有如下的5种。
① 使用SharedPreferences存储数据:通过XML文件将一些简单的配置信息存储到设备中。只能在同一个包内使用,不能在不同的包之间使用。
② 文件存储数据:在Android中读取/写入文件,与Java中实现I/O的程序完全一样,提供了openFileInput()和openFileOutput()方法来写入和读取设备上的文件。
③ SQLite数据库存储数据:SQLite是Android自带的一个标准数据库,支持SQL语句,是一个轻量级的嵌入式数据库(将在本项目中做重点介绍)。
④ 使用ContentProvider存储数据:主要用于在应用程序之间进行数据交换,从而能够让其他应用读取或者保存某个ContentProvider的各种数据类型。
⑤ Internet网络存储数据:通过网络提供的存储空间来上传(存储)和下载(获取)在网络空间中的数据。

一、使用SharedPreferences存储数据

SharedPreferences方式是Android读取外部数据最简单的方法,常用于将用户个性化设置的字体、颜色、位置等参数信息保存在文件中,适用于存储数据量较少的场合。这种方式用来存储关键字(Key)和值(Value)成对映射的数据结构(key,value),其中value值只能是int、long、boolean、string和float五种基本数据类型。事实上该方式相当于一个HashMap,不同之处在于HashMap的value值可以是任何对象,而SharedPreferences中的值只能存储基本数据类型。使用SharedPreferences存储数据主要应用SharedPreferences和SharedPreferences.Editor接口,下面分别进行介绍。
SharedPreferences接口的主要方法如下。
① boolean contains(String key):检查在数据文件中是否已经包含有关键字key的数据。
② SharedPreferences.Editor edit():获取Editor对象。Editor对象的作用是存储key-value键值对数据,可对数据进行修改,并确保参数值在提交存储后保持状态一致。
③ boolean getBoolean(String key,boolean defValue):从参数数据中检索出关键字key所对应的boolean数值类型。参数key为需要检索的关键字;参数defValue为在数据文件中查找不到相应的关键字时返回的默认数值。
④ float getFloat(String key,float defValue):从数据文件中检索出关键字key所对应的float数值类型。
⑤ int getInt(String key,int defValue):从数据文件中检索出关键字key所对应的int类型数值。
⑥ long getLong(String key,long defValue):从数据文件中检索出关键字key所对应的long类型数值。
⑦ String getString(String key,String defValue):从数据文件中检索出关键字key所对应的String类型的数值。
SharedPreferences.Editor接口的主要方法如下。
① void apply():将Editor中对参数进行修改的数据向SharedPreferences对象提交。
② SharedPreferences.Editor clear():删除SharedPreferences对象中的所有数据。
③ boolean commit():将Editor中对参数进行修改的数据向SharedPreferences对象提交。
④ SharedPreferences.Editor putBoolean(String key,boolean value):对关键字key设置boolean类型的数值value。
⑤ SharedPreferences.Editor putFloat(String key,float value):对关键字key设置float类型的数值value。
⑥ SharedPreferences.Editor putInt(String key,int value):对关键字key设置int类型的数值value。
⑦ SharedPreferences.Editor putLong(String key,long value):对关键字key设置long类型的数值value。
⑧ SharedPreferences.Editor putString(String key,String value):对关键字key设置String类型的数值value。
⑨ SharedPreferences.Editor remove(String key):从参数中删除关键字为key的数值。 下面举例说明实现通过SharedPreferences对登录用户的用户名与密码进行读取和写入的两个方法。可以将这两个方法放入两个菜单选项中进行调用测试,也可以放在项目二实训项目一的用户登陆界面中进行测试:用户首次登陆时,保存其密码和账号;在下一次登陆时,则检查输入的账号、密码是否与之前保存的账号、密码相匹配。

图像说明文字

代码分析如下。
代码中的getSharedPreferences(String name,int mode)方法是Activity类的方法,用于获取SharedPreferences对象。其中,参数name表示保存参数信息的文件名,如果该名字的文件不存在,则在执行提交操作后自动新建;参数mode表示操作模式,体现了对文件操作的权限,其值在Context类中定义。

  • MODE_PRIVATE或者0:是默认操作,表示文件为私有数据,只能被调用的应用程序访问。在该模式下,写入的内容会覆盖原文件的内容。
  • MODE_APPEND或者32768:为追加模式,会检查文件是否存在,存在就向文件中追加内容,否则就创建新文件。
  • MODE_WORLD_READABLE或者1:表示创建的文件可以被其他应用读取。
  • MODE_WORLD_WRITEABLE或者2:表示创建的文件可以被其他应用写入。
    如果希望创建的文件既可以被其他应用读取又可以被其他应用写入,则:

图像说明文字

如果要读取配置文件信息,只需要直接使用SharedPreferences对象的getXXX()方法即可。而如果要写入配置信息,则必须先调用SharedPreferences对象的edit()方法,使其处于可编辑状态,然后再调用putXXX()方法写入配置信息,最后调用commit()方法提交更改后的配置文件。实际上,SharedPreferences采用XML格式将数据存储到设备中,可通过DDMS中的File Explorer在“/data/data//shares_prefs”下进行查看。以上述数据存储结果为例,可以看到一个名为user.xml的文件,打开后有如下数据:

图像说明文字

二、文件存储数据

Android文件读取或者写入介质分两类:一类是ROM(只读内存)文件存储,另一类是SD卡文件存储。两者之间的差别在于,SD卡存储的内容可以共享给任何应用程序,而ROM存储数据则由应用程序本身独有,其他应用程序无法访问。
1.ROM存储数据
使用Activity类的openFileInput()和openFileOutput()方法来操作设备上的文件,创建的文件默认存放在“/data/data//files”目录下,如在包名为【com.company.business】的程序中创建一个【date.txt】文件,存放路径将是【/data/data/com.company.business/files/ date.txt】。在默认状态下,文件不能在不同的程序之间共享,这两个方法只支持读取该应用目录下的文件,若读取非自身目录下的文件将会抛出FileNotFoundException异常。
FileInputStream openFileInput(String name):读取名字为name的文件,返回FileInputStream流。因为文件存放在默认的路径下,因此不需要在参数name中指定文件的所在路径。
FileOutputStream openFileOutput(String name,int mode):向名字为name的文件中写入数据,返回FileOutputStream流。如果文件不存在,Android将会自动创建。和getSharedPreferences方法的mode参数类似,这里的参数mode取值为0或者MODE_PRIVATE时,表示所创建的文件为私有数据,只能被调用的程序存取;取值为MODE_APPEND时,表示将数据追加到已有的文件中;取值为MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE时,可以控制其他的应用程序是否有读取或者写入文件的权限。
关键的实现代码如下,建议将这两个方法放入两个菜单选项中进行调用,以查看程序效果。
(1)读取文件代码。

图像说明文字

(2)写入文件代码。

图像说明文字

2.SD卡存储数据
SD卡存储数据的操作实际就是J2SE的I/O操作,数据可以在任何应用程序间共享。在SD卡中若找不到对应的文件将会抛出FileNotFoundException异常,创建的文件存放在用户指定的SD卡目录下。
进行SDK操作,需要在配置文件AndroidManifest.xml中添加如下权限声明:
< uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
< uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
获取SD卡的根目录可以通过android.os.Environment.getExternalStorageDirectory().getPath()方法来获取。下面列出关键的实现代码。
(1)读取文件代码。

图像说明文字

(2)将数据写入文件代码,参数sName表示文件名,data表示写入文件的数据。

图像说明文字

三、SQLite数据库存储数据

计算机应用程序常用的数据库有SQL Server、My SQL、Oracle、DB2等。SQLite则是一款著名的应用于嵌入式设备的开源轻量级数据库,占用的系统资源非常少,在嵌入式设备中,只需要350 KB的内存,这也是Android等手持设备系统采用SQLite数据库的重要原因之一。目前SQLite的最新版本为3.7.13,可以在官方网站http://www.sqlite.org获得相应源代码和文档。SQLite数据库有如下优点。
① 独立性:SQLite数据库的核心引擎不需要依赖第三方软件。
② 隔离性:SQLite数据库中的所有信息(如表、视图、触发器等)都包含在一个文件中,便于管理和维护。
③ 跨平台:SQLite目前支持大部分操作系统,例如,Unix(Linux、Mac OS-X、Android、iOS)、Windows(Win32、WinCE、WinRT)等平台。
④ 多语言接口:SQLite数据库支持多语言编程接口,例如,C、PHP、Perl、Java、C#、Python等。
⑤ 安全性:SQLite数据库支持ACID事务,通过数据库级的独占性和共享锁来实现独立事务处理。这就意味着多个进程可以在同一时间从同一数据库读取数据,但只能有一个可以写入数据。
⑥ 轻量级:SQLite大致由3万行C代码完成,只需要一个较小的动态库,就可以享有其全部功能。
⑦ 零配置:无需安装和管理配置。
Android集成了SQLite数据库,所以每个Android应用程序都可以使用SQLite数据库。对于熟悉SQL的开发人员来说,在Android开发中使用SQLite较为简单。不同的是Android没有JDBC链接数据库的概念,因为这样会耗费很多系统资源,为此Android系统提供了一些链接数据库的API,在开发SQLite应用程序时,程序员应先学会使用这些API。Android系统下每个程序的数据存放在“/data/data/(package name)/”目录下,数据库则存放在“…/dababases/”目录下,即“/data/data/(package name)/databases/”下,当然,也可以指定新的存储目录。
SQLite数据库的一个比较重要的概念是数据库版本。在SQLite的开发中常需要指定数据库的版本号。这是因为在移动开发中,随着应用程序的升级,手机用户中可能有多个旧版本并存,这些旧版本都可能需要升级到最新版本,但要执行的升级操作可能不一样,尤其在保留用户旧数据的情况下。
SQLite数据库的使用方法将在本项目后面的任务中进行详细的介绍。

四、使用ContentProvider对外共享数据

一个应用程序可以创建自己的数据,这些数据对该应用程序来说是私有的,外界无法看到,因此也无从得知数据是通过数据库、文件还是网络存储的。对此,可以为外界提供一套与程序中的数据打交道的标准及统一的接口,以便进行添加、删除、查询、修改等操作。
在本章的第3.7节中会详细介绍如何使用ContentProvider实现对外共享数据。

五、Internet网络存储数据

网络存储相对于以上四种存储方式显得不够经济,这是因为移动终端的网络稳定性和产生的流量费用对用户来说是一种负担。对于重要的实时数据,或需要发送给远端服务器处理的数据,可以使用网络进行实时发送,例如,移动办公、实时天气预报、即时通信软件QQ等应用程序产生的数据。
Android网络存储使用标准的Web协议,编写方法与Java标准版实现网络应用程序的方法类似。先建立一个Web服务器端,负责接收Andorid提交的数据,然后返回XML数据或JSON格式的数据。Android的客户端则负责向服务器发送相应的请求并接收相应的数据,过程代码示例如下。

图像说明文字

3.2 添加联系人记录

一、任务分析

本任务需要实现的效果如图3-1所示。
手机通讯录用于记录联系人的姓名、单位、电话号码、QQ、地址等信息,是手机常用且必备的功能。添加联系人记录的模块主要是为用户提供录入联系人基本信息的界面,并将用户录入的数据保存起来。要完成本次任务,需要思考如下问题。
(1)在手机上如何存储应用程序的数据?
(2)如何把界面上的数据保存到手机上?

图像说明文字

二、相关知识

1.SQLiteDatabase类
SQLiteDatabase类是一个最终类,用于管理SQLite数据库,类中有创建、删除、执行SQL命令以及执行其他常见的数据库管理任务的命令。SQLite除了提供execSQL()和rawQuery()等直接对SQL语句进行解析的方法外,还针对INSERT、UPDATE、DELETE和SELECT等操作专门定义了相关的方法,其主要方法介绍如下。
(1)void beginTransaction():以独占模式开始事务。执行该方法后,如果代码在后面不执行setTransactionSuccessful()方法,对数据库的操作将会回退。
(2)int delete(String table,String whereClause,String[] whereArgs):删除数据库中的某些行。其中参数table表示需要删除记录的表;参数whereClause表示删除数据的条件,如果取值为null则表示删除所有的记录;参数whereArgs表示where语句中表达式的“?”占位参数列表,参数只能为String类型。
(3)boolean deleteDatabase(File file):删除数据库。参数file表示数据库文件的路径。
(4)void endTransaction():结束事务。
(5)void execSQL(String sql):执行一条不是Select或者其他有数据返回的SQL语句,使用参数sql表示要执行的SQL语句。运行该方法不会返回任何数值,建议熟悉SQL的开发者使用该方法。
(6)long getMaximumSize():返回数据库可以扩展到的最大空间。
(7)String getPath():返回数据库文件所在的路径。
(8)int getVersion():返回数据库的版本。
(9)long insert(String table,String nullColumnHack,ContentValues values):向数据库中插入一行数据。其中,参数table表示需要插入记录的表名。参数nullColumnHack为可选参数,如果取值为null,则values取值不能为空,这是因为SQL不允许在没有给列命名的情况下,插入一个完全空的行;如果该参数取值不是null,那么其所提供的列名表示如果value值为空,则向该列插入一个空值。参数values表示要插入行数据的ContentValues对象,即列名和列值的映射。
(10)boolean isOpen():判断数据库当前是否已经被打开。
(11)boolean isReadOnly():判断数据库当前是否以只读的方式被打开。
(12)SQLiteDatabase openDatabase(String path,SQLiteDatabase.CursorFactory factory,int flags,DatabaseErrorHandler errorHandler):根据指定的方式打开数据库。其中,参数path表示打开或者创建的数据库文件;参数factory表示一个可选的工厂类,用于创建一个查询时需要使用的游标对象,默认取值为null;参数flags表示数据库访问的方式,取值有OPEN_READWRITE、OPEN_READONLY、CREATE_IF_NECESSARY或者NO_LOCALIZED_COLLATORS;参数errorHandler用于当SQLite报告数据库发生毁坏时进行错误处理。
(13)Cursor query(String table,String[] columns,String selection,String[] selectionArgs,String groupBy,String having,String orderBy,String limit):查询给定的表,并把查询结果以Cursor游标对象的形式返回。
① 参数table表示查询的表名。
② 参数columns表示返回的列。使用字符串数组表示,若取值为null则返回所有的列。
③ 参数selection指定需要返回哪些行的where条件语句(此处不需要包括SQL的关键字where),若取值为null则表示返回所有的行。
④ 参数selectionArgs表示where语句中表达式的?占位参数列表,参数只能为String类型。
⑤ 参数groupBy表示对结果集进行分组的group by语句(此处不需要包括SQL的group by关键字),若取值为null将不对结果集进行分组。
⑥ 参数having表示对分组结果集设置条件的having语句(此处不需要包括SQL的having关键字)。必须是配合groupBy参数使用,若取值为null将不对分组结果集设置条件。
⑦ 参数orderBy表示对结果集进行排序的order by语句(此处不需要包括SQL的order by关键字)。若取值为null将对结果集使用默认的排序,通常情况下是不排序的。
⑧ 参数limit限制查询返回的行数,若取值为null将不限制返回的行数。
(14)Cursor rawQuery(String sql,String[] selectionArgs):执行一条SQL查询语句,并把查询结果以Cursor对象的形式返回。其中,参数sql表示需要执行的SQL语句字符串;参数selectionArgs表示SQL语句中表达式的“?”占位参数列表,参数只能为String类型。
(15)void setTransactionSuccessful():将当前的事务标记为成功。
(16)void setVersion(int version):设置数据库的版本号。参数version表示新的数据库版本号。
(17)int update(String table,ContentValues values,String whereClause,String[] whereArgs):更新数据库中的行数据。其中参数table表示需要插入记录的表名;参数values表示要更新行数据的ContentValues对象,即列名和列值的映射;参数whereClause指定符合更新条件行的SQL Where语句,若取值为null则更新所有的行。
2.SQLiteOpenHelper类
Android提供SQLiteOpenHelper类来辅助创建一个SQLite数据库,通过继承SQLiteOpenHelper类可以轻松地创建数据库,并支持数据库的版本更新管理。这两项功能是SQLiteDatabase类所欠缺的,因此在一般情况下,将SQLiteOpenHelper和SQLiteDatabase两个类结合起来使用。
SQLiteOpenHelper是一个抽象类,根据开发应用程序的需要,封装了创建和更新数据库使用的逻辑,是实现SQLite数据库的一个关键类。编程时,应定义一个SQLiteOpenHelper的子类,至少应实现onCreate(SQLiteDatabase db)和onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)两个方法,下面对该类进行详细的介绍。
SQLiteOpenHelper类有两种构造方法,格式分别如下。
① SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version)。其中,参数context表示上下文;参数name表示数据库文件的名字,对于在内存中的数据库取值为null;参数factory表示可选的工厂类,用于创建一个查询时用到的游标对象,默认取值为null;参数version表示数据库的版本,取值从1开始。
② SQLiteOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory,int version,DatabaseErrorHandler errorHandler)。前四个参数的含义和第一个构造函数一样,第五个参数errorHandler用于当SQLite报告一个数据库发生毁坏错误时进行错误处理。
SQLiteOpenHelper类的常用方法介绍如下。
① void close():关闭任何打开的数据库对象。
② String getDatabaseName():返回已经被打开的SQLite数据库的名字。
③ SQLiteDatabase getReadableDatabase():创建或者打开一个数据库,返回一个SQLite数据库对象。当数据库的磁盘空间已满时,将以只读的方式访问数据库。
④ SQLiteDatabase getWritableDatabase():创建或者打开一个数据库,返回一个SQLite数据库对象。当数据库的磁盘空间已满时,执行该方法会报错。
⑤ void onCreate(SQLiteDatabase db):当数据库首次被创建时调用该方法,用于创建表和初始化表中的数值。如果数据库之前已经被创建,则该方法将不会被执行。参数db表示SQLite数据库对象,在方法中根据需要对该对象填充表和初始化数据。
⑥ void onDowngrade(SQLiteDatabase db,int oldVersion,int newVersion):当数据库需要降级时调用该方法。其中,参数db表示SQLite数据库对象;参数oldVersion表示旧的数据库版本号;参数newVersion表示新的数据库版本号。
⑦ void onOpen(SQLiteDatabase db):数据库被打开时该方法被自动调用。例如,执行getReadableDatabase()或getWritableDatabase()时,若数据库已存在,则调用onOpen方法,否则调用onCreate方法。
⑧ onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion):当数据库需要升级时调用该方法。其中,参数db表示SQLite数据库对象;参数oldVersion表示旧的数据库版本号;参数newVersion表示新的数据库版本号。
更新数据库的版本是通过重新构造SQLiteOpenHelper对象来实现的。假设数据库原来版本是2,下面为示例代码:
SQLiteOpenHelper databaseHelper=new SQLiteOpenHelper(Context context,String string, null,1);
SQLiteDatabase db=databaseHelper.getReadableDatabase();
由于SQLiteOpenHelper构造函数的最后一个参数是1,跟原来的版本不同,而且是把版本降级了,所以系统就会调用onDowngrade(SQLiteDatabase db,int oldVersion,int newVersion)方法来处理降级事件。
若SQLiteOpenHelper构造函数的最后一个参数是3,那么就比原来的版本要高,所以就会调用onUpgrade(SQLiteDatabase db,int oldVersion,int newVersion)方法来处理升级。
下面的示例代码展示了如何继承SQLiteOpenHelper类来创建一个名为My_DB.db的数据库。

图像说明文字

在Activity类中用MyDataBase类来生成一个对象,即可创建一个数据库:
MyDataBase database= new MyDataBase (this);
可以在“/data/data//database/”目录下找到数据库文件My_DB.db,接着可以利用SQL语句创建一个表。

图像说明文字

在表创建好之后,可以向表中插入数据:

图像说明文字

3.ContentValues类
ContentValues类和Hashtable类较为相似,它用于存储一组键值对,可以被ContentResolver类处理,但是它存储的键值对中的键是一个String类型,而值都是基本类型。ContentValues类作为一个重要的参数在SQLiteDatabase中的insert、update等方法中使用。下面对该类进行详细的介绍。
ContentValues类有3种构造方法,格式分别如下。
① ContentValues():使用默认的初始大小创建一个空集。
② ContentValues(int size):使用指定的初始大小size值创建一个空集。
③ ContentValues(ContentValues from):复制给定的集合from,用于创建一组集合数值。
ContentValues类的常用方法介绍如下。
① void clear():清空集合中的所有数值。
② boolean containsKey(String key):如果集合中包含了指定的关键字key,则返回true,否则返回false。
③ Object get(String key):返回关键字key对应的数值,返回数值类型为Object,通常还需要进行强制类型转换。
④ void put(String key,Integer value):将一个整形数值加入到集合中,其中参数key表示集合中的关键字;参数value表示要添加的数据。ContentValues类还有很多put方法,主要的区别是第二个参数为其他数据类型,例如,put(String key,Byte value)、put(String key,Float value)、put(String key,Short value)、put(String key,byte[] value)、put(String key,String value)、put(String key,Double value)、put(String key,Long value)、put(String key,Boolean value)。
⑤ void remove(String key):将某个关键字key的数值从集合中删除。
⑥ int size ():返回集合中数值的个数。
创建ContentValues的一般步骤是首先定义一个ContentValues对象,然后使用put方法将值放入集合,下面为示例代码。
ContentValues values= new ContentValues();
values.put("name","小明");
values.put("age",17);
4.Toast类
Toast类用于向用户显示帮助或者提示信息。与Dialog不同的是,Toast类显示的信息在短暂地显示一段时间后会自动消失。信息可以是简单的文本,也可以是复杂的图片及其他内容(显示一个View)。
使用Toast构造函数创建Toast对象较为麻烦,最便捷的生成Toast对象的方法是直接调用其静态方法。
① makeText(Context context,int resId,int duration):创建一个标准的Toast对象,只包含一个来自资源文件的文字视图。其中,参数context表示上下文;参数resId表示字符串资源文件的ID;参数duration表示显示消息的时间长度,取值为LENGTH SHORT或者LENGTH L ONG,分别表示短时间或者长时间显示视图或文本提示。
② makeText(Context context,CharSequence text,int duration):创建一个标准的Toast对象,只包含一个文字视图。参数text表示消息的文字内容,可用字符串表示。
调用上述两个方法之一创建Toast对象之后,再调用该对象的show()方法,就可以打开Toast的信息提示。
当需要调整Toast在屏幕上的显示位置时,可以使用setGravity(int gravity,int xOffset,int yOffset)方法。其中参数gravity表示显示信息的起点位置,常用的取值有Gravity. CENTER_
HORIZONTAL、Gravity.CENTER_VERTICAL、Gravity.CENTER、Gravity.TOP、Gravity.LEFT、Gravity.RIGHT、Gravity. BOTTOM;参数xOffset表示水平位移,大于0则向右移,小于0则向左移;参数yOffset表示垂直位移,大于0则向下移,小于0则向上移。如Toast对象.setGravity(Gravity.CENTER_VERTICAL,0,0)表示把Toast对象定位在屏幕的正中间。
当需要设置Toast在屏幕上显示的持续时间时,可以使用setDuration(int duartion)方法。其中,参数duartion表示时间,单位是毫秒(ms),LONG_DELAY为3500 ms,SHORT_DELAY为2000 ms。需要注意的是,参数duartion若取其他数值,并不会使显示时间更长或者更短。
当需要修改Toast显示的文本内容时,可以使用setText(int resId)或者setText(CharSequence s)方法。
当需要设置Toast在屏幕中的显示视图时,可以使用setView(View view)方法。其中,参数view表示视图。该方法一般用于在Toast中显示自定义的视图。
下面的示例用于说明创建Toast的三种方法,程序的主界面效果如图3-2所示。

图像说明文字

(1)定义项目的布局XML文件,命名为【toast_demo.xml】,在界面上以垂直方式显示3个按钮,代码如下。

图像说明文字

(2)定义一个Toast调用的信息显示布局文件【toastinfo.xml】,在文件中定义一个Image View控件和一个TextView控件。

图像说明文字

(3)编写Android程序。

图像说明文字

代码补充说明:上述代码使用到了LayoutInflater类。在实际开发中LayoutInflater类较为重要,作用类似于findViewById()。不同点是LayoutInflater用来解析res/layout/下的XML布局文件,并进行实例化。对于一个没有被载入或者想要动态载入的界面,都可以使用LayoutInflater.inflate()来加载;而findViewById()则是用于查找XML布局文件下的具体widget控件(如Button、TextView等)。对于一个已经载入的界面,可在Activity程序中直接调用findViewById()方法获得其中的界面元素。

三、任务实施

1.创建联系人录入界面
(1)创建项目:创建一个Android Project,建项目名称为【MyContacts】,包名为【com.demo. pr3】,选择Android SDK 2.2版本,并创建一个Activity,将其命名为【MyContactsActivity】。
(2)设计用户输入界面,提供姓名、单位、电话、QQ和地址5项信息供用户录入,每项录入信息使用一个LinearLayout嵌套一个TextView和EditText子元素来表示,然后再将这些LinearLayout作为上一级LinearLayout的子元素。以下是布局页面的XML代码。

图像说明文字 图像说明文字

2.创建数据库的操作类
(1)创建一个SQLiteOpenHelper的子类MyDB,用于对数据库进行管理,主要包括创建表,并对表数据进行增加、删除、修改和查找,以及打开和关闭数据库等基础功能操作。
首先定义该类的主要数据成员:

图像说明文字

下面介绍该类主要方法的实现。
① 构造函数。

图像说明文字

② 打开SQLite数据库连接。

图像说明文字

③ 关闭SQLite数据库连接操作。

图像说明文字

④ 创建表。

图像说明文字

⑤ 添加数据:…部分省略的代码与creatTable方法一样。

图像说明文字

⑥ 修改数据:…部分省略的代码与creatTable方法一样。

图像说明文字

⑦ 删除数据:…部分省略的代码与creatTable方法一样。

图像说明文字

⑨ 判断表是否存在:对表进行查询,如果出错表示数据库中不存在所要查询的表。

图像说明文字

(2)创建一个表示联系人信息的类【User.java】。
该辅助类的作用主要是采用面向对象的方法管理联系人的数据,以便于对联系人表中的字段统一进行设置和获取操作。

图像说明文字

(3)创建ContactsTable.java操作类。
ContactsTable类用于封装联系人表中数据的操作,例如将界面上的数据保存到数据库中,主要实现是通过调用MyDB类的方法。
① 定义类的成员变量。

图像说明文字

② 在构造方法中创建表。

图像说明文字

③ 创建添加数据到数据库的方法,将联系人数据放入到一个ContentValues对象中。

图像说明文字

3.将界面上数据保持到数据库
创建一个显示添加数据界面的Activity类AddContactsActivity。首先根据界面要显示的控件,相应地定义该类的主要数据成员。

图像说明文字

下面介绍该类主要方法的实现。
(1)从布局文件中获得界面上的控件对象。

图像说明文字

(2)设置界面上返回和保存的菜单。

图像说明文字

(3)实现保存和返回的处理操作,将输入界面中的信息先保存在User对象中,然后再通过前面定义的ContactsTable类addData方法将数据保存到SQLite数据库的表中。

图像说明文字

3.3 修改联系人记录

一、任务分析

本任务需要实现的效果如图3-3所示。
对联系人信息进行修改,首先需要将联系人的信息从SQLite数据库读出并显示在界面上。当用户修改数据之后,再将更新后的记录写回到存储器当中。要完成本次任务,需要思考以下三个问题。
(1)在Android中如何切换两个Activity程序?
(2)如何把界面上修改后的记录更新到存储器中?
(3)如何知道用户单击修改的是哪一条记录?

图像说明文字

二、相关知识

1.Cursor类
Cursor是一个游标类,可以在一组数据库查询结果的集合上前后移动,进而检索每一行的数据。下面对该类进行详细的介绍。
(1)void close():关闭游标,释放所有的资源,并使游标变为无效。
(2)int getColumnCount():返回列的个数。
(3)int getColumnIndex(String columnName):根据字段的列名返回其索引值如果不存在则返回−1。其中参数columnName表示字段的列名。
(4)String getColumnName(int columnIndex):根据字段的索引值,返回字段的列名。
(5)String[] getColumnNames():返回一组表示所有字段列名的字符串数组。
(6)int getCount():返回游标中行的个数。
(7)double getDouble(int columnIndex):将请求的列数值以double类型返回,参数columnIndex表示列的索引值。
(8)float getFloat(int columnIndex):将请求的列数值以float类型返回,参数columnIndex表示列的索引值。
(9)int getInt(int columnIndex):将请求的列数值以int类型返回,参数columnIndex表示列的索引值。
(10)long getLong(int columnIndex):将请求的列数值以Long类型返回,参数columnIndex表示列的索引值。
(11)int getPosition():返回游标的当前位置。
(12)short getShort(int columnIndex):将请求的列数值以short类型返回,参数columnIndex表示列的索引值。
(13)String getString(int columnIndex):将请求的列数值以String类型返回,参数columnIndex表示列的索引值。
(14)boolean isAfterLast():判断游标是否指向最后一行之后的位置。
(15)boolean isBeforeFirst():判断游标是否指向第一行前面的位置。
(16)boolean isClosed():判断游标是否被关闭。
(17)boolean isFirst():判断游标是否指向第一行。
(18)boolean isLast():判断游标是否指向最后一行。
(19)boolean isNull(int columnIndex):如果指定的列数值是null,则返回true。参数columnIndex表示列的索引值。
(20)boolean move(int offset):将游标从当前位置向前或者向后移动到一个新的位置。参数offset表示一个相对的移动距离。
(21)boolean moveToFirst():将游标移到第一行。
(22)boolean moveToLast():将游标移到最后一行。
(23)boolean moveToNext ():将游标移到下一行,如果游标当前已经在最后一行,执行该方法将会返回false。
(24)boolean moveToPosition(int position):将游标从当前位置向前或者向后移动到一个新的位置。参数position表示一个绝对的目的位置,取值范围为大于等于−1,小于等于集合中记录的个数。
下面举例说明如何使用Cursor类来对数据库的查询结果数据进行遍历,其中db是一个SQLiteDatabase类对象,person是需要查询的个人信息表。
String sName;
Int iAge;
Cursor cursor=db. rawQuery("select * from student",null);
while(cursor.moveToNext()){
//获取游标当前行的name列数值
sName=cursor.getString(cursor.getColumnIndex("name"));
//获取游标当前行的age列数值
iAge=cursor.getInt(cursor.getColumnIndex("age"));

}

2.Bundle类
Bundle类可以用于不同Activity之间的数据传递,将数据打包到Intent对象中,辅助Intent对象携带数据。Bundle类与Java的集合类Map类似,用于存放key-value键值对形式的值,提供了各种常用类型的put()/get()方法,例如,putString()/getString()、putInt()/getInt()。其中,put ()用于向Bundle对象放入某种类型的数据,get ()方法用于从Bundle对象获取某种类型的数据,操作与Intent对象的putExtra()方法相对应。Bundle内部实际使用HashMap类型的变量来存放put ()方法放入的值。
3.Activity的切换
一个Android应用程序可以有多个Activity类,因此常常需要处理各Activity类之间的切换。例如用户登录Activity在验证账号和密码成功之后,需要打开应用程序的主界面Activity。 Intent的作用就是在程序运行时连接两个不同的组件,为它们之间传递数值,可以理解为意图。Android的Activity、Service和BroadcastReceiver三种基本组件,都是由Intent的运行绑定机制激活的,这三种组件在传递Intent的实现上各不相同。Intent在运行时将各个组件相互结合在一起。可以把Intent看作是请求从其他组件执行行动的使者,不管该组件是否属于该应用程序。
Intent调用目标组件有两种方式:一种是显式Intent,即明确指出目标组件;另一种是隐式Intent,即只是指出调用组件的特征,由系统决定调用哪个组件。以Activity为例,在AndroidManifest.xml文件中,使用元素的子元素< action android:name>定义待选的Activity会被哪些动作调用。
对于Activity和Service,Intent定义要执行的动作,如:查看或发送某种类型的数据,并可以指定需要操作的数据的URI。例如,一个Intent可以为一个Activity传送要求,要求其显示图像或打开网页。在某些情况下,可以启动一个Activity去接收请求、进行处理并将结果返回Intent中。例如,发出一个Intent让用户可以选择某个人的通讯录,并让这个Intent返回一个指向所选择通讯录的URI。
对于BroadcastReceiver,Intent只简单定义一个广播公告,如广播显示设备电池电量低,只需包括一个已知的显示“电池电量低”的动作字符串。
对于其他类型的组件,ContentProvider并不是被Intent激活的,而是由ContentResolver激活的。ContentResolver和ContentProvider一起处理共享数据的访问事务。
下面是激活每种类型组件的不同方法。
① 开启一个Activity:通过传递一个Intent对象到startActivity() 或者startActivityForResult()方法中(注:当希望Activity返回结果时调用后一个方法。
② 开启一个Service:通过传递一个Intent对象到startService ()方法或者bindService()方法。
③ 开启一个Broadcast:通过传递一个Intent对象到sendBroadcast()、sendOrderedBroadcast()或者sendStickyBroadcast()方法。
④ 在ContentResolver上调用query()方法,对ContentProvider执行查询操作。
本节主要介绍Intent在Activity中的使用方法。Intent是从一个Activity到达另一个Activity的引路者,包含当前Activity、目标Activity、分类、Activity之间切换所需的动作、传送的数据、标志位和附加的信息等。在使用Intent时,可根据需要来填写这些数据。
Intent类有六种构造方法,格式分别如下,其中构造方法①~④是隐式的,构造方法⑤、⑥种是显式的。
① Intent():创建一个空的Intent。
② Intent(Intent o):复制一个已有的Intent对象。参数o表示已有的Intent对象。
③ Intent(String action):根据给定的动作创建一个Intent对象。其中,参数action表示Intent触发动作的名字。Android系统提供了很多标准的Activity动作和Broadcast动作。例如,android.intent.action.MAIN表示主程序入口,android.intent.action.CALL_BUTTON表示打开系统应用中的拨号界面,ACTION_AIRPLANE_MODE_CHANGED表示用户切换飞行模式的Broadcast动作。详细的标准Activity动作和Broadcast动作定义可以查看官方文档 (http://developer.android.com/reference/android/content/Intent.html)。
④ Intent(String action,Uri uri):根据给定的动作和数据网址来创建一个Intent对象。其中参数action表示Intent触发动作的名字;uri表示动作处理的数据所在的位置。Uri类的使用在下一节进行介绍。
⑤ Intent(Context packageContext,Class<?> cls):为特定的组件建立一个Intent对象。其中,参数packageContext表示实现当前类的应用上下文;参数cls表示用于Intent的组件类。Contex表示一个开发上下文的接口,代表某种开发环境。有些组件或者控件在应用时需要知道它们所在的环境或上下文信息。
⑥ Intent(String action,Uri uri,Context packageContext,Class<?> cls):根据特定的动作和数据,为特定的组件建立一个Intent对象。
Intent类的常用方法介绍如下。 (1)Intent提供了各种putExtra()方法,作用是向Intent对象添加不同类型的数据。putExtra()方法的参数数量有两种形式:一种是putExtra(String name,datatype value),它带有两个参数,其中参数name表示数据的名字,value表示数据值,datatype表示某种数据类型或者数组,如putExtra(String name,int value)、putExtra(String name,int[] value);另一种是只带有一个参数的,如putExtras(Bundle extras)和putExtras(Intent src)。
(2)Bundle getExtras():获得Intent携带的一组扩充数据,即其之前在其他组件通过putExtra()方法添加的数据。
(3)Uri getData():返回Intent正在操作数据的Uri。
(4)Intent setClass(Context packageContext,Class<?> cls):通过指定Class对象的方式设置需要跳转的目标组件。其中,参数packageContext表示实现当前类的应用上下文,一般的取值形式是:当前Activity的类名.this;参数cls表示要跳转到的目的Activity,一般的取值形式是:Activity类名.class。
在一个应用程序内往往会通过Intent对象来指定需要跳转的目标Activity,并调用startActivity(Intent intent)或者startActivityForResult(Intent intent,int requestCode)方法来启动该Activity。这两个方法的区别是:前者调用一个新的Activity,不需要接收其返回的结果;而后者除了调用新的Activity之外,还将其处理结果返回到前一个执行的Activity的onActivityResult(int requestCode,int resultCode,Intent data)方法中。参数requestCode表示请求码,用于识别调用的Activity。
设置完Intent对象后,若通过startActivity或者startActivityForResult方法启动新的Activity时,设置的值也会一同传递到新启动的Activity中。在新的Activity中,可以通过在oncreate方法内调用getIntent.getExtra()方法获得一个Bunble对象,并调用相应的Get方法获取之前Intent对象利用putExtra方法写入的值。
如果通过putExtra()方法来传递一个复杂的数据,那么该数据必须是可序列化的。例如,要传递一个Person对象的实例,则该类必须实现Serializable接口。

图像说明文字


说明
使用putExtra()方法传递一个实现了Serializable接口的类的实例对象时,这个类中所有的成员也必须是可序列化的,否则会抛出异常。


一个简单Intent实现如下。

图像说明文字

下面举例说明在Intent中附加数据的两种方法。
(1)先将数据写入到Bundle对象中,然后再将Bundle对象传入Intent对象。

图像说明文字

(2)直接把数据逐个添加到Intent对象中。

图像说明文字

启动当前Activity的Intent对象是通过getIntent()方法,而Bundle对象则可以通过Activity类的getIntent().getExtras()方法返回。
下面举例说明Activity之间如何切换并相互传递数据,效果如图3-4所示。在示例中有两个Activity程序,分别是IntentDemo和IntentDemo2,其中IntentDemo中有1个EditText用于接收用户输入的1个字符串,要求把该字符串转换成大写状态,但把转换的工作交给IntentDemo2来做,并把转换成大写的字符串再返回给IntentDemo,由其负责显示。

图像说明文字

(1)构建IntentDemo所需的界面布局,文件名为【intent_demo.xml】。

图像说明文字

(2)编写IntentDemo程序代码。

图像说明文字

(3)构建IntentDemo2所需界面的布局,文件名为【intent_demo2.xml】。

图像说明文字

(4)编写IntentDemo2程序代码。

图像说明文字

3.URI类
URI(Universal Resource Identifier)称为通用资源标识符,表示需要操作的数据,特别是包含了数据所在的位置。Android的每一种资源都可以使用URI表示。URI包含主机名、片段标识符、相对URI等部分。一般情况下,开发者并不需要自己定义URI,只需要调用即可。
URI一般具有如下形式:

< scheme name> : scheme-specific-part

scheme name:命名URI名称空间的标识符,例如,http、file、tel、smsto、mailto、content等。
scheme-specific-part:冒号把scheme name与scheme-specific-part分开,scheme-specific- part的语法和语义(意思)由URI的名称空间决定。
以ContentProvidr为例,URI主要包含了两部分信息:需要操作的ContentProvider和要进行操作的ContentProvider中的数据,如图3-5所示。

图像说明文字

(1)scheme name:ContentProvider(内容提供者)的scheme name已经由Android规定,命名为content://。
(2)主机名(Authority):用于唯一标识ContentProvider,外部调用者可以根据此标识来找到它。如果把ContentProvider看作是一个网站,那么主机名就是其域名。
(3)路径(Path):用来表示要操作的数据,路径的构建应根据业务而定。

  • 要操作student表中ID为5的记录,构建的路径为/student/5。
  • 要操作student表中ID为5的记录的name字段,构建的路径为student/5/name。
  • 要操作student表中的所有记录,构建的路径为/student。
  • 要操作表中的记录,可以构建路径为/。当然要操作的数据不一定来自数据库,也可以是文件、XML或网络等其他 存储方式。如要操作XML文件中student节点下的name节点,可以构建路径:/student/name。

URI类的主要作用是解析标识符。如果要把一个字符串转换成URI,可以使用URI类中的parse()方法:
Uri uri=Uri.parse("content://com.ljq.provider.personprovider/person")
4.修改表记录的SQL语法
继续使用3.2节中MyDataBase类定义的对象database
sql="update person set name='小花' where name='小东'";
database.db.execSQL(sql);

三、任务实施

本任务将要创建一个新的Activity类UpdateContactsActivity,它负责更新通讯录,并对3.2节中的类进行修改,具体步骤如下。
(1)设计联系人信息修改界面。该界面与添加联系人记录的界面类似,不同之处在于前者的文本输入框显示了从数据库中读取的联系人信息,后者的所有文本框都为空。
(2)在ContactsTable.java类中实现根据联系人ID来获取联系人信息的方法。

图像说明文字

(3)将联系人数据赋值到用户界面。

图像说明文字

(4)在ContactsTable.java类中添加更新updateUser()方法。

图像说明文字

(5)在UpdateContactsActivity上设置【返回】和【保存】按钮。

图像说明文字

(6)实现【保存】按钮函数,调用(4)中的updateUser方法将输入界面中的信息更新到记录存储当中。

图像说明文字

3.4 查找号码记录

一、任务分析

本任务需要实现的效果如图3-6所示。
当有很多条联系人数据存储在数据库中时,为了方便用户快速找到所需信息,需要提供一项查找功能。查询联系人界面应提供一个文本框,由用户输入联系人的部分信息后,单击【查询】按钮,界面显示出符合条件的联系人。要完成本次任务,需要思考如下两个问题。
(1)如何把查询条件传递给SQLite数据库并进行模糊查询?
(2)如何把查询结果的每条记录显示在界面上?

图像说明文字

二、相关知识

1.Dialog类
对话框一般是出现在当前Activity之上的小窗口。处于下层的Activity失去焦点,对话框与用户进行交互,一般用于提示信息。对话框在程序中不是必备的,但是合适的对话框能够增加应用的友好性。常见的对话框情景有:用户登录、网络正在下载、下载成功或失败的提示、电池电量耗尽等。
Dialog类是其他类型对话框的父类,可以继承Dialog自定义对话框,在任务实施部分将会演示如何定义一个查找对话框。
Dialog类有两种构造方法,格式分别如下。
① Dialog(Context context):使用默认的框架样式创建一个对话框,参数context表示上下文。
② Dialog(Context context,int theme):使用定制的样式创建一个对话框,参数theme表示描述窗口主题的样式资源。可以在项目的“res/values”目录下定义XML样式文件。
Dialog作为一个可以显示的对话框,很多实现方法和Activity相同,在此不再一一赘述,下面主要对其常用的方法进行介绍。
① void cancel():退出对话框。
② void dismiss ():退出对话框,功能和cancel()相同。
③ void hide():隐藏但不退出对话框。
④ void onCreate(Bundle savedInstanceState):在对话框创建时被系统调用,利用该方法初始化对话框的设置,包括调用setContentView(View)。
⑤ void setContentView(View view):设置对话框的视图,参数view表示需要显示的内容。
⑥ void setContentView(int layoutResID):设置对话框的视图,参数layoutResID表示资源布局文件的ID。
⑦ void setTitle(int titleId):设置对话框的标题,参数titleId表示标题文本的资源标识符。
⑧ void setTitle(CharSequence title):设置对话框的标题,参数title表示标题文本的内容。
⑨ void show ():启动对话框并显示在屏幕上。
Dialog有5种不同类型的对话框的子类,其中警告对话框AlertDialog已经在项目二中进行了介绍,这里介绍另外4种类型对话框。
① 字符选择对话框(Character PickerDialog):让用户选择设定的字符。例如图3-7所示的数字0~9和字母A~F就是通过程序定义并用对话框显示出来的。
② 进度对话框(Progress Dialog):显示一个圆形进度条或者一个长条形进度条。由于ProgressDialog是AlertDialog的扩展,所以Progress Dialog也支持按钮事件。ProgressDialog类提供静态方法ProgressDialog show(Context context,CharSequence title,Char Sequence message, boolean indeterminate)用于显示进度对话框。其中,参数context表示上下文;参数title表示进度对话框的标题;参数message表示对话框的内容;参数indeterminate表示进度是否不确定,如果取值为true,表示不确定。
③ 日期选择对话框(Date Picker Dialog):让用户选择一个日期,界面示例如图3-8所示。
④ 时间选择对话框(Time Picker Dialog):让用户选择一个时间,界面示例如图3-9所示。
圆形进度条显示对话框的示例如下,要求实现图3-10所示的效果。

图像说明文字

下面为进度对话框显示的示例:

图像说明文字

2.ListView类
ListView是比较常用的控件,以垂直列表的形式展示具体内容。内容可以是文字和图片,并且能够根据数据的数量自适应显示。本任务在显示联系人名单时就用到了ListView。
除此之外,常用的View控件有:TextView、ImageView、ProgressBar、AutoComplete TextView、Button、CheckBox、EditText、ImageButton、ImageSwitcher、RadioButton、RadioGroup、SeekBar、Spinner、TabHost、TabWidget、TableLayout、WebView等。.
这些控件有一个类似的构造方法:类名(Context context)。例如ListView可以使用构造方法ListView(Context context),若在Java代码中生成一个View实例,就可以用这个万金油式的构造方法。
创建ListView有3种方法:第1种是在XML文件中使用ListView控件;第2种是直接继承ListActivity;第3种是在Java文件中直接新建ListView对象。建议在XML文件中使用ListView控件,这样可以较好地控制其属性。
在Activity中构造一个ListView:

ListView listView=new ListView(this);

下面介绍ListView两个比较重要的方法。
(1)void setAdapter(ListAdapter adapter)方法:向ListView对象添加适配器。参数adapter表示适配器。这样当数据发生变化之后,ListView会自动更新显示。
(2)void setOnItemClickListener(AdapterView.OnItemClickListener listener)):对适配器中的数据项添加监听器,当数据项被单击时,将触发相应的操作。参数listener表示监听器。下面是一般的编写方法,其中listView为ListView对象,onItemClick方法的position参数表示用户在列表中单击数据的位置,数值从0开始。

图像说明文字

3.Adapter类
Adapter在Android应用程序中起着非常重要的作用,提供对数据的访问权限,也负责为每一项数据产生一个对应的View。Adapter的应用非常广泛,可看作是数据源和ListView、Spinner、Gallery、GridView等UI显示控件之间的桥梁。当数据源发生变化时,Adapter可以使UI控件的数据自动更新显示。图3-11展示了Adapter在数据和UI控件之间的作用。比较常用的基础数据适配器有BaseAdapter、ArrayAdapter、SimpleAdapter、SimpleCursorAdapter、CursorAdapter等。其中ArrayAdapter、CursorAdapter、SimpleAdapter等还直接提供了getFiter()方法对数据进行过滤的功能,例如用户在文本框中输入部分字符,控件可以只显示匹配的内容。

(1)ArrayAdapter:数组适配器,使用起来较为简单,但只能用于显示文字。ArrayAdapter常用的构造函数是ArrayAdapter(Context context,int textViewResourceId,T[] objects),可创建一个用于装配数据的数组装配器。其中,参数context表示上下文,一般用当前的Actvity表示;参数textViewResourceId表示布局文件,该布局文件的作用是描述数组中每一条数据的显示布局(如定义一个TextView),既可以使用Android内部提供的布局文件(如android. R.layout.simplelist_item_1,android.R.layout.simple_expandable list_item_1,详细定义可以在SDK安装目录下查看),也可以使用开发者自己定义的布局文件;objects表示要显示的数据数组。listView会根据这3个参数,遍历数组中的每一条数据,每读出一条,则将其显示到第2个参数textViewResourceId对应的布局中,这样就形成了我们看到的ListView的显示效果。
下面举例进行说明,实现如图3-12所示的效果。

图像说明文字

① 定义布局文件【listviewdemo.xml】,添加一个ListView,代码如下。

图像说明文字

② ListViewDemo程序代码如下。

图像说明文字

(2)SimpleCursorAdapter:游标适配器,从数据库中读取数据并显示在列表上。其原理是:从Cursor游标取出数据并用ListView进行显示,然后把指定的数据字段列映射到TextView或者ImageView中。下面介绍SimpleCursorAdapter类的构造函数。
SimpleCursorAdapter(Context context,int layout,Cursor c,String[] from,int[] to):创建一个游标适配器。其中,参数context表示上下文,一般用当前的Actvity表示;参数layout表示布局文件,作用与ArrayAdapter构造函数中的textViewResourceId一样;参数c表示数据库游标,如果游标不可用,则取值为null;参数from表示绑定到显示在UI上的数据字段列表;参数to表示对应显示参数from列的控件。从构造函数来看,以数据库作为数据源时才适合使用SimpleCursorAdapter。
下面的示例用以实现将系统通讯录的联系人信息显示在列表中。

图像说明文字

代码说明如下。
① getContentResolver()返回一个ContentResolver对象,用于访问某个ContentProvider。
② 用query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)方法获得一个指定URI的Cursor对象。其中参数projection表示返回的列,参数selection表示SQL的where查询条件,参数selectionArgs表示where语句中表达式的?占位参数列表,参数sortOrder表示要进行升序或降序的字段。
注意还需要在【AndroidMainfest.xml】文件中把读取通讯录的权限打开。

< uses-permission android:name="android.permission.READ_CONTACTS" />

(3)SimpleAdapter:具有较强的扩展性,能自定义出各种效果。不仅可以添加ImageView(图片),还可以添加Button(按钮)、CheckBox(复选框)等。SimpleAdapter类的构造函数是:SimpleAdapter(Context context,List<? extends Map> data,int resource,String[] from,int[] to)。其中,参数context表示上下文,一般用当前的Actvity表示;参数data表示由Map构成的List,List集合中的每个数据由一个Map集合组成,而每个Map集合的数据对应显示在ListView的每一行。要求Map集合的第一个关键字必须是String类型;参数resource表示布局文件,作用与ArrayAdapter构造函数中的textViewResourceId一样;参数from表示绑定到UI上的数据字段列表;参数to表示对应显示参数from列的控件。
下面的示例通过直接继承ListActivity类,利用SimpleAdapter创建一个能够显示图片和文字的程序界面,如图3-13所示。ListActivity类其实就是含有一个ListView控件的Activity类。如前面两个例子,若直接在普通的Activity中加入一个ListView也完全可以取代ListActivity。

图像说明文字

代码中的<…>部分是用于限制集合的数据类型。出于简化代码的目的,也可以将getData()方法中的<…>代码全部删除掉,即:>,。从上面的代码可以看出,要达到显示效果需要创建一个名为【simpleadapter_demo.xml】的布局文件,其包含一个用于显示图片的ImageView控件和两个显示文本的TextView控件。

图像说明文字

(4)BaseAdapter:通过继承BaseAdapter子类,可以在列表上添加处理事件,例如按钮单击处理。自定义BaseAdapter的子类能够灵活实现各种适配器数据显示效果,因此也能够实现前面ArrayAdapter、SimpleCursorAdapter、SimpleAdapter的效果。
定义一个BaseAdapter的子类,需要实现如下方法。
① int getCount():返回适配器所表示的数据项数量。
② Object getItem(int position):返回数据集中指定位置的数据项。其中,参数position表示位置。
③ long getItemId(int position):返回列表中指定位置的行ID。其中,参数position表示位置。
④ View getView(int position,View convertView,ViewGroup parent):返回一个显示数据集指定位置数据的视图。参数position表示某个位置的数据;参数convertView用于显示每一数据项的视图;参数parent表示父视图,例如Spinner、ListView、GridView等,即显示最终会被附加到的父级视图。需要注意的是:当调用这个方法时,参数convertView是循环再用的。因此在编写该方法的实现代码时需要判断convertView对象是否为空,若为空,则需要为它实例化一个视图对象并配置相关的显示属性。getView方法非常重要,用于定义数据如何显示在ListView中。
ListView在开始绘制的时候,系统会首先调用BaseAdapter 适配器的getCount()方法,根据其返回值得到ListView的长度,然后根据这个长度,调用getView()逐一绘制每一行。若getCount()返回值为0时,列表将不显示数值。
当视图关联的数据集有变动时(在本项目中,将表示用户信息的数组users作为显示的数据源,见3.9项目的完整代码),可调用BaseAdapter类的notifyDataSetChanged()方法通知ListView通过视图刷新数据,这样就避免了人工重新检索和更新数据显示的繁琐。
下面举例说明如何定义BaseAdapter适配器:

图像说明文字

在Actvity程序中定义MyAdapter对象,通过ListView对象setAdapter方法去调用:

MyAdapter mybaseadapter=new MyAdapter (this,{ "周末","周一","周二","周三","周四","周五"});
listView.setAdapter(mybaseadapter);

相对于ArrayAdapter、SimpleAdapter等其他适配器,BaseAdapter能够更灵活地去配置数据在ListView中的显示。尤其是在ListView中每一行的数据显示风格可能存在差异的情况,更加需要用BaseAdapter来进行判断,并设置每一行的显示风格。下面对它的使用步骤进行总结,以便于开发者更好地理解它的使用方法。
(1)定义一个BaseAdapter的子类,然后通过Android自带的纠错功能“Add unimplemented methods”来自动生成所需要实现的方法。系统自动会生成四个方法“getCount()”、“getItem()”、“getItemId()”、“getView()”。此外,开发者自己要另外创建一个构造方法,将需要显示的数据(其实就是数据源)以集合的方式传递进来,因此对应在子类中定义一个集合对象,用于接收构造方法传递过来的数据源。例如,

图像说明文字

(2)分别实现系统自动生成的四个方法,其中核心需要实现的方法是getCount()和getView()。getCount()方法是返回集合中数据的个数。getItem()返回对应位置(其实就是某一行)的数据、getItemId()返回对应数据的ID、getView()返回对应数据的显示风格。
(3)方法getView(int position, View convertView, ViewGroup parent)决定了ListView中每行数据的显示,因此需要在该方法编写具体的数据显示方式,主要有下面四个步骤。
① 首先判断参数convertView是否为空,若为空,则生成一个布局LayoutInflater对象,加载列表项布局。

图像说明文字

② 通过convertView.findViewById()方法获得列表项布局文件的各种控件。
③ 根据项目本身数据显示的需要,对第②步得到的控件进行赋值或者属性设置。其中可能用到判断语句,即根据不同的数据取值,显示不同的风格控件。
④ 在方法的最后返回convertView对象:return convertView;。
(4)上面的步骤完成了一个BaseAdapter子类的定义,在使用中,只需使用该子类定义一个对象,然后将这个对象作为参数赋值到ListView.setAdapter()中即可。
4.查找记录的SQL语法
向3.2节的MyDataBase类中添加查询表数据的方法selectData:

图像说明文字

继续使用3.2节中MyDataBase类定义的对象database,显示查询结果:

图像说明文字

三、任务实施

(1)定义一个XML布局页面【find.xml】作为查找界面,为用户提供输入搜索信息的文本框,并设置【查询】和【取消】按钮。

图像说明文字

(2)在ContactsTable.java类中添加查询findUserByKey(String key)方法。该方法使用符号“%”来模糊匹配联系人的用户名、手机号、QQ号等信息,使用一个游标Cursor对象表示查找结果,然后通过循环调用取出游标中的数值。

图像说明文字

(3)定义用户查找对话框类,继承于Dialog类,显示用户布局页面【find.xml】,并处理【查找】和【退出】按钮的相关事件。

图像说明文字

3.5 查看联系人记录

一、任务分析

本任务需要实现的效果如图3-14所示。
本次任务的实现方法和3.3节具有一定的相似性,区别在于3.3节任务的作用是可以修改联系人记录,而本任务只是将联系人的信息显示在界面上,不允许用户修改联系人的信息。要完成本次任务,需要思考如何在界面上只显示信息?

图像说明文字

二、任务实施

(1)设计用户显示界面,用于显示联系人的元素都使用TextView类,这样用户便无法对数据进行修改。

图像说明文字

(2)将联系人数据赋值到用户界面。

图像说明文字

3.6 删除号码记录

一、任务分析

为用户单击菜单上的“删除联系人”操作时,应提示用户“是否确定删除选定的记录”,如果用户选择【确定】,则将该记录删除;如果用户选择【取消】,则不执行删除操作。要完成本次任务,需要思考如下两个问题。
(1)如果在界面上为用户提供删除操作的信息提示?
(2)如何将一条记录从SQLite中删除?

二、相关知识

删除一条记录使用SQLiteDatabase类的delete(String table,String whereClause,String[] whereArgs)方法。从参数可以看出,删除是以记录的where条件参数作为依据的。示例如下。

图像说明文字

三、任务实施

(1)在ContactsTable.java类中添加deleteByUser(User user)方法,该方法根据联系人表中的主键ID来删除联系人。
(2)利用AlertDialog定义一个显示窗口,让用户选择是否确定删除某个联系人记录。如果用户选择【确定】,则删除该联系人;否则,返回原有联系人显示列表。

图像说明文字

3.7 对外共享数据

一、任务分析

Android系统本身已经自带了一个通讯录,本次任务是将本项目创建的通讯录数据和Android系统的通讯录共享,实现将本项目创建的通讯录数据导入到系统通讯录中。要完成本次任务,需要思考如下两个问题。
(1)如何理解Android的系统通讯录?
(2)如何访问系统通讯录,并对其进行操作?

二、相关知识

1.ContentProvider类
通过SQLiteDatabase创建的数据库只能被其创建者使用,其他的应用不能访问,所以Android提供了ContentProvider来实现对外数据的共享。一个程序如果允许其程序操作自己的数据,就可以定义自己的ContentProvider,然后在【AndroidManifest.xml】中注册,其他Application可以通过URI有选择性地操作程序中的数据。若使用其他方法也可以对外共享数据,但数据访问方式会因数据存储方式而不同,如文件存储、SharedPreferences数据存储、SQLite数据存储等访问数据的方法均不相同。
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成。ContentResolver类的作用是能够访问ContentProvider,提供了insert、delete、query、update等主要接口。要获取ContentResolver对象,可以使用Activity提供的getContentResolver()方法,即:
ContentResolver contentresolver=getContentResolver();
下面对ContentResolver的主要方法进行介绍。
(1)Uri insert(Uri url,ContentValues values):向给定URI的表中插入一行数据。
(2)Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder):根据给定的URI数据,进行数据查询,查询结果集用游标对象表示。其中,参数uri表示数据的URI;参数projection表示返回的列;参数selection表示指定需要返回哪些行的where语句(不包括SQL的关键字where),若取值为null则表示返回所有的行;参数selectionArgs表示where语句中表达式的“?”占位参数列表,参数只能为String类型;参数sortOrder表示对结果集进行排序的order by语句(不包括SQL的order by关键字),若取值为null将对结果集使用默认的排序,通常不进行排序。
(3)update(Uri uri,ContentValues values,String where,String[] selectionArgs):根据给定的URI更新行数据。其中,参数uri表示数据的URI;参数values表示要插入行数据的ContentValues对象,即列名和列值的映射;参数where表示指定需要更新哪些行的where语句(不包括SQL的关键字where),若取值为null则表示更新所有的行;参数selectionArgs表示where语句中表达式的“?”占位参数列表,参数只能为String类型。
(4)delete(Uri url,String where,String[] selectionArgs):删除给定URI的记录。其中,参数url表示数据的URI;参数where表示删除数据的条件where语句(不包括SQL的关键字where),如果取值为null则表示删除所有的记录;参数whereArgs表示where语句中表达式的“?”占位参数列表,参数只能为String类型。
Android中的通讯录数据即通过ContentProvider实现数据共享。实际上,在Android系统中已经存在很多共享的URI。可以使用ContentResolver来操作不同表之间的数据,如MediaStore.Images.Media.INTERNAL_ CONTENT_URI(或content://media/internal/images)表示访问设备上存储的所有图片。
当应用程序需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面的方法。

图像说明文字

为了能让其他应用程序找到该ContentProvider,第二步需要在【AndroidManifest.xml】中使用对该ContentProvider进行配置。采用authorities(主机名/域名)对Content Provider进行唯一标识。

图像说明文字

其中,android:name需要设置为ContentProvider的子类名;android:authorities则指定了在content://样式的URI中标识ContentProvider的字符串。另外,可以设置android:readPermission和android:writePermission两个属性,来分别指定对ContentProvider中数据进行读、写操作的权限。也可以在ContentProvider类的onCreate()方法中调用setReadPermission()和setWrite Permission()方法来动态指定权限。
2.Android系统通讯录
Android系统的通讯录已经通过ContentProvider封装。我们可以通过SDK提供的URI和字段来对其进行增加、删除、修改、查询等操作。对系统通讯录进行操作,需要在【AndroidManifest.xml】文件中添加如下的读写权限。

< uses-permission android:name="android.permission.WRITE_CONTACTS">
< uses-permission android:name="android.permission.READ_CONTACTS">

ContactsContract类主要用于表示通讯录提供者和应用程序之间的约定,包括支持的URI和列的定义。其中ContactsContract.Data.CONTENT_URI(或content://com.android.contacts/ contacts)表示系统联系人的URI。
ContentProvider自己管理一个SQLite数据库文件,名为【contacts2.db】。该文件的路径为“/data/data/com.android.providers.contacts/databases/contacts2.db”。从手机模拟器DDMS的操作界面图中可以看出,除了Android的通讯录信息放在“/databases”目录之外,Android系统的其他ContentProvider也放在相应的目录下,如图3-15所示。

图像说明文字

利用【File Explorer】选项卡中右上角的【Pull a file from the device】按钮,可以将contacts 2.db数据库文件从手机模拟器中导出到计算机上。
SQLite的官方网站http://www.sqlite.org/cvstrac/wiki?p=ManagementTools列出了很多查看SQLite数据库的软件。这里以SQLite Administrator(http://sqliteadmin.orbmu2k.de/)为例进行介绍。需要注意的是,SQLite Administrator不能够读取存放在中文目录下的SQLite数据库,因此需要将contacts2.db放在没有中文的目录下。
在手机模拟器的系统通讯录程序上创建两个联系人信息,名字分别为adam和peter,电话号码分别为386588888和356288888,图3-16所示为打开的contacts2.db的data表信息。

图像说明文字

针对data表,Android提供了ContactsContract.Data类进行管理。该类表示的是一个通用表,可以保存任何类型的联系人数据,如电话号码和E-mail地址。每类数据存储在一个给定的行中,并指定行的MIMETYPE值。MIMETYPE的取值确定了表中DATA1~DATA15列的含义。DATA1列是索引列,通常用于存放在查询条件中最常用的数据。例如某一行表示邮件地址,那么DATA1应该存放邮件地址,而DATA2等其他列则用来存放邮件地址类型的辅助信息。DATA15一般用于存放二进制数据。 ContactsContract.CommonDataKinds类中定义了一些数据类型的种类,例如,ContactsContract. CommonDataKinds.Phone、ContactsContract.CommonDataKinds.StructuredName、 ContactsContract. Common DataKinds.E-mail,下面分别进行介绍。
ContactsContract.CommonDataKinds.Phone类表示电话号码,具有以下重要属性。
① CONTENT_ITEM_TYPE:指明数据MIME类型。
② NUMBER:指明电话号码。数据将存放在DATA1列中。
③ TYPE:指明电话号码的类型。例如,TYPE_HOME表示家庭电话;TYPE_MOBILE表示移动电话。数据将存放在DATA2列中。
ContactsContract.CommonDataKinds.StructuredName类表示联系人的名字,具有以下的重要属性。
① CONTENT_ITEM_TYPE:数据MIME类型。
② GIVEN_NAME:指明联系人名字。数据放在DATA2列中。
③ FAMILY_NAME:指明联系人姓氏。数据放在DATA3列中。
④ PREFIX:尊称前缀,例如,Mr、Ms、Dr。数据放在DATA4列中。
ContactsContract.CommonDataKinds.E-mail表示E-mail信息,具有以下重要属性。
① CONTENT_ITEM_TYPE:指明数据MIME类型。
② ADDRESS:指明E-mail地址,存放在DATA1列中。注意ADDRESS名在Android 3.0版本后才有定义,在此版本前的版本中可以直接使用DATA1。
③ TYPE:指明邮件类型。例如,TYPE_HOME表示家庭邮件、TYPE_WORK工作邮件。存放在DATA2列中。
编写将数据插入到通讯录的代码,实际上就是创建一个ContentValues对象,然后分别为上述每一类数据所需的属性进行赋值,最后将ContentValues插入到通讯录中。例如,

图像说明文字

三、任务实施

定义一个importPhone方法,首先获得系统通讯录的URI,然后分别将本项目的姓名和电话号码插入到系统通讯录中。

图像说明文字

3.8 设计主界面

一、任务分析

本任务需要实现的效果如图3-17所示。
用户在打开手机联系人程序时,主界面将以列表形式显示出当前存储器中的联系人信息。手机通讯录程序有添加、编辑、删除、查看信息、查询和导入到手机电话簿、退出7项功能。将这些操作集成在主界面上,可以方便用户随时进行各种操作。要完成本次任务,需要思考如下两个问题。
(1)如何将存储器中的主要信息显示在主界面上?
(2)如何把添加、编辑、删除、查看信息、查询和导入到手机电话簿、退出7项功能较好地集成在主界面上?

图像说明文字

二、任务实施

(1)设计用户显示界面,主界面由ListView和菜单组成。

图像说明文字

(2)将存储器中的联系人信息显示在列表(ListView)上,将【添加】、【编辑】、【删除】、【查看信息】、【查询】、【导入到手机电话簿】和【退出】等功能作为主界面的菜单项。

图像说明文字 图像说明文字 图像说明文字

3.9 完整项目实施

手机通讯录程序由多个类和XML布局文件组成,下面分别对每个类的实现进行详细介绍。
(1)Android项目配置文件:【AndroidManifest.xml】。

图像说明文字

(2)手机通讯录程序主界面类:【MyContactsActivity.java】。

图像说明文字 图像说明文字 图像说明文字 图像说明文字 图像说明文字 图像说明文字 图像说明文字

3.9 完整项目实施

手机通讯录程序由多个类和XML布局文件组成,下面分别对每个类的实现进行详细介绍。
(1)Android项目配置文件:【AndroidManifest.xml】。

图像说明文字

(2)手机通讯录程序主界面类:【MyContactsActivity.java】。

图像说明文字 图像说明文字 图像说明文字

(3)手机通讯录程序数据库作类:【MyDB.java】。

图像说明文字 图像说明文字 图像说明文字 图像说明文字

(4)手机通讯录程序添加、编辑,查看信息操作类:【ContactsTable.java】。

图像说明文字 图像说明文字 图像说明文字 图像说明文字 图像说明文字

(5)手机通讯录程序辅助类:【User.java】。

图像说明文字

(6)手机通讯录程序添加联系人界面类:【AddContactsActivity.java】。

图像说明文字 图像说明文字

(7)手机通讯录程序修改联系人界面类:【UpdateContactsActivity.java】。

图像说明文字 图像说明文字

(8)手机通讯录程序显示联系人界面类:【ContactsMessageActivity.java】。

图像说明文字 图像说明文字

(9)手机通讯录程序编辑和添加联系人布局XML:【/layout/edit.xml】。

图像说明文字 图像说明文字

(10)手机通讯录程序查找联系人布局XML:【/layout/find.xml】。

图像说明文字

(11)手机通讯录程序主页面布局XML:【/layout/main.xml】。

图像说明文字

(12)手机通讯录程序显示联系人布局XML:【/layout/message.xml】。

图像说明文字

目录

推荐用户

同系列书

  • 移动应用UI设计

    张晓景 胡克

    本书中通过基础加案例的方式向同学们介绍了Photoshop在手机UI领域中的应用。全书中从UI的基础讲起,针对...

    ¥49.80
  • 移动平台UI交互设计与开发

    陈燕 戴雯惠 魏娜 许伟刚

    本书内容分为设计篇和开发篇,以企业全真项目和经典案例为载体,内容覆盖Photoshop在智能手机应用图标设计、...

    ¥45.00
  • Android移动应用开发项目教程

    李新辉 邹绍芳 陈云志 周昕 吴红娉

      本书通过精心设计的7个工作项目,全程贯彻“做中学”理念,先实践认知,后理论拓展,由浅入深,让读者逐步掌握A...

    ¥46.00
  • Android项目开发入门教程

    张伟华 朱东、伊雯雯

    本书主要围绕Android应用程序开发基础、界面设计、Activity、ContentProvider、数据存...

    ¥35.00
  • Android应用程序设计教程

    李华忠 梁永生 刘涛

      全书共分成8章,主要内容包括Android开发环境构建、Android屏幕布局、Android控件Widg...

    ¥39.80
  • 跨平台的移动Web开发实战(HTML5+CSS3)

    从跨平台的移动Web开发实际应用的角度理解HTML5和CSS3的新元素和新功能,合理选取教学内容。本书设置了8...

    ¥49.80
  • Android移动开发项目式教程(第2版)

    谢景明 钟闰禄 陈长辉 冯敬益 王志球

    本书内容主要包括7 部分,第1 部分讲解搭建Android 开发环境的方法,第2 部分讲解在Android...

    ¥42.00
  • HTML 5移动平台的Java Web实用项目开发

    本书以真实购物网站为项目原型,以购物网站为载体,将购物网站的功能模块合理划分为8个教学单元:导航栏和信息提示设...

    ¥45.00
人邮微信
本地服务
人邮微信
教师服务
二维码
读者服务
读者服务
返回顶部
返回顶部