临近期末,目前在进行Android课程设计,对于我这个压根不怎么学习的孩子来说,刚开始的完全是懵逼的,后来花两天时间把书认真的看了一遍,然后结合网上的的一些经验,也算是勉强把这个手机通讯录做出来了。不过不能直接在自己的手机上运行,因为自己手机上的通讯录权限早已被更改。晚饭前才把课程设计的报告也已经写完了。根据自己的课程设计报告总结下开发的过程以及自己的心得~
先附上源码:手机通讯录开发MyContacts
更:老师说我界面做的太丑,于是我又改了下界面,顺便解决了调用相机拍照的问题~~界面的图片就没有重新加载到此文了,感兴趣的可以看源代码~~
一.需求分析
手机通讯录是现代安卓手机里面最基本的功能,而且是安卓APP开发的一个经典案例,对于学习巩固Android应用技术有很大的帮助。对于当代智能手机,至少应该具备的的一项功能就是要能够存取联系人,并进行拨打电话,发送短信等功能,该APP就是为了实现该功能设计的。
二.数据库
智能设备的联系人存储在SQLite数据库之中,名称为Contacts2,查看路径为DDMS/FileExplorer/data/data/com.android.provider.contacts/database/contacts2。可以利用SQLiteManager打开该数据库。数据库Contacts2里面有很多的表,其中最主要的是4张表:
1.Contact表
_id :表的 ID,关键字。 display_name:联系人名称
photo_id:头像的 ID,如果头像未设置,该字段就为空
last_time_contacted: 最后的通话时间
lookup :是一个持久化的储存 因为用户可能会改名字,但不能改 lookup 值
2.Data表
raw_contact_id:通过 raw_contact_id 可以找到 raw_contact 表中相对的数据
data1 到 data15 这里保存着联系人的相关信息、联系人名称、联系人电 话号码、 电子邮件、备注等。
mimetype_id:
vnd.android.cursor.item/photo -> 4 该行 data15 代表照片
vnd.android.cursor.item/phone_v2 -> 5 该行 data1 代表电话号码
vnd. android.cursor.item/name -> 6 说明该行 data1 代表联系人名称
3.Phone_lookup表
data_id : 通过 data_id 可以找到 data 表中相对的数据。
raw_contact_id : 通过 raw_contact_id 可以找到 raw_contact_表中相对的数据。 normalized_number: 将每个电话号码逆序排列
4.Raw_contacts表
contact_id:引用 contacts 表的_id 字段
account_name:指明该联系人是从哪个账户上同步下载下来的。
account_type:邮件帐户的类型
sync1:说明该联系人信息是与哪个网址进行交互(同步联系人信息)
三.设计过程
1)内容设计
设计一个手机通讯录,能够对其进行增删改查操作,并且能够调用系统的拨打电话,发送短信功能。
1.主界面MainActivity:主界面顶部有一个搜索框,输入姓名,点击搜索按钮弹出选择对话框。可以对被搜索的联系人进行打电话,发短信,删除等操作,被搜索的该联系人如果不存在数据库中的话,则不会弹出该选择对话框;主界面中间是 用ListView和适配器将存在于数据库中的联系人以条目的形式显示出来,在每 个条目中显示该联系人的头像,姓名,电话号码。在每个条目的右边有两个 Image
Button分别用来给该联系人打电话和发短信。同时点击该条目进入联系人修改界面EditAcvity,长按该条目弹出对话框,对该条目的联系人进行删除操作;在主界面的底部还有两个按钮:拨号主界面:点击该按钮进入该界面可以直接输入电话号码进行拨号。添加:点击“添加”按钮可以进入添加联系人EditActivity界面。
2.添加联系人界面EditActvity:该界面上端是一个ImageView,点击该图片按钮会弹出一个对话框可以进行头像选择操作:调用系统相机或者从系统图库里选取一张照片作为该联系人的头像;头像下面是三个EditText,分别用联系人姓名,电话,邮箱信息的输入;该界面的底端是两个按钮:添加:点击该按钮将上述信息存入数据库,然后返回主界面并将信息显示在主界面。取消:点击该按钮返回主界面。
3.修改联系人界面ChangeActvity:该界面和添加联系人界面相似,但是当进入该界面时,显示的是需要修改的联系人信息,可以对这些信息进行修改。点击“修改”按钮时修改数据库中的信息,然后返回主界面并将修改后的信息显示在主界面,点击“取消”按钮则返回主界面。
2)方法设计
主界面MainActivity:
1.搜索功能:输入姓名,并根据姓名搜索数据库中的相关信息。首先用一个EditText,,输入该需要查询的联系人姓名,找到该EditText对应的id,然后用getText().toString()方法得到输入的值。在EditText的旁边有一个搜索按钮,为该按钮设置一个监听事件,点击该按钮,根据EditText中得到的姓名进行查询。如果该联系人在数据库中存在则利用Builder builder = new AlertDialog.Builder(MainActivity.this)弹出一个对话框,在该对话框中可以对选中的联系人进行打电话,发短信,删除等操作。其中打电话和发短息都是调用模拟器系统的功能,删除操作的话,是根据姓名返回该联系人的ID,然后根据该ID对联系人进行删除操作。
2.联系人显示功能:对数据库中的联系人信息进行显示。首先定义一个游标phoneCursor用来访问数据库,得到数据库中的联系人数据。然后定义一个适配器MyListAdapter继承BaseAdapter对得到数据进行绘制,这样才能将数据库的信息以适配器中的布局item一条一条的显示出来。
对显示的联系人进行操作:对该条目采取匿名内部类的方式设置了一个监听器setOnItemClickListener,点击某个联系人的时候根据该联系人条目的id获取到该联系人的相关信息,并启用一个Intent存储这些信息,然后将该信息传递到联系人信息修改界面ChangeActivity,并经该信息在修改界面进行显示。对该条目还采取匿名内部类的方式设置了一个监听器setOnItemLongClickLis
tener,长按该条目时,获取该条目的id和姓名,然后利用Builder builder = newAlertDialog.Builder(MainActivity.this)弹出一个对话框,选择确定删除或者取消删除该联系人操作,若点击确定,则根据长按该条目得到的姓名访问数据库,根据姓名查找到数据库中的ID,然后根据ID删除该联系人,使用startActivity(newIntent(MainActivity.this,MainActivity.class))刷新当前界面。
3.拨号主界面功能:由于用户可能想要拨打一些并不存于在通讯录中间的号码,这是就需要用药拨号主界面。对“拨号主界面”按钮用匿名内部类的方式设置了一个监听器,点击该按钮的时候,调用系统的拨打电话功能,并进入系统的拨号主界面,输入电话号码,进行拨号。
4.添加联系人功能:对“拨号主界面”按钮用匿名内部类的方式设置了一个监听器,点击该按钮的时候,调用Intent进入添加联系人界面EditActivity。
联系人添加界面EditAcitivity:
1.选择头像功能:点击头像所在的ImageButton,然后利用Builder builder = newAlertDialog.Builder()弹出一个一个对话框:“拍照”“选择系统图片”若选择“拍照”,则调用系统相机进行拍照,由于相机拍摄的照片太大,因而要调用系统工具对图片进行剪切,然后在该界面调用一个Intent返回该图片并在ImageView中显示。若选择“选择系统图片”则进入系统的图库进行头像选择,并且在该界面调用一个Inten返回该图片并在ImageView中显示。
2.姓名电话邮箱输入功能:使用三个EditText,分别用findViewById分别得到这三个EditText的ID,然后用getText().toString()函数便能得到在EditText中输入的值。
3.添加功能:对于“添加”按钮利用匿名内部类设置一个监听器,当点击该按钮的时候,首先向 RawContacts.CONTENT_URI 执行一个空值插入,获取系统返回的数据表中rawContactId,然后将输入的头像,姓名,电话,邮箱等信息依次存入数据库系统之中,一个一个的值存进去,这样有助于存取。然后调用一个Intent返回主界面,主界面会显示新增的联系人信息。
4.取消功能:对于“取消”按钮利用匿名内部类设置一个监听器,当点击该按钮的时候,调用一个Intent返回主界面,联系人的信息没有变化。
联系人修改界面ChangeActvity:该页面的功能和联系人添加界面EditAcitiVity的功能相似,不同的是:
1.信息获取功能:利用一个Intent获取在主界面的条目点击事件中利用Intent传过来的该需要修改的联系人的相关信息,然后利用.setText()函数对传递过来的信息在相应的EditText和ImageButton进行显示(图片信息是用.setImageBitmap(photo)函数进行显示)。
2.更新信息功能:对于“修改”按钮利用匿名内部类设置一个监听器,当点击该按钮的时候,首先删除在数据库中删除该联系人原来的信息,然后将更新后的联系人信息插入数据库之中,这样便实现了对联系人的修改功能(删除和插入功能和前面所说的原理是的一样的)然后调用一个Intent返回主界面,主界面会显示修改过的联系人信息。
3)页面展示
主界面MainActivity
搜索联系人弹出的界面
拨号主界面
长按删除联系人弹出的界面
添加联系人界面EditActvity
修改联系人界面ChangeActvity
拨号界面
短信发送界面
4)具体代码实现
搜索功能:
1.设置监听器对“搜索”按钮的点击事件进行监听,产生相应的操作
Button button2=(Button)findViewById(R.id.button2);
button2.setOnClickListener(new OnClickListener(){
public void onClick(View v){
name = (EditText)findViewById(R.id.editText1);
String m= name.getText().toString();
testQueryandOperate(m);
}
});
2.testQueryandOperate(m)函数的主要代码
//使用ContentResolver查找联系人数据
Cursor cursor = getContentResolver().query(ContactsContract.Contacts
.CONTENT_URI, null, null, null, null);
//遍历查询结果,找到所需号码
while (cursor.moveToNext()) {
//获取联系人ID
String contactId = cursor.getString(cursor.getColumnIndex
(ContactsContract.Contacts._ID));
//获取联系人的名字
String contactName = cursor.getString(cursor.getColumnIndex
(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
if (name.equals(contactName)) {
//使用ContentResolver查找联系人的电话号码
Cursor phone = getContentResolver().query (ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null, null);
if (phone.moveToNext()) {
Final String phoneNumber=phone.getString(phone.getColumnIn
dex(ContactsContract.CommonDataKinds.Phone.NUMBER);}
}
}
根据该函数得到的该联系人的电话号码和姓名,利用Builder可以弹出一个对话框对该联系人进行打电话,发短信,删除等操作。
联系人显示功能:
在主界面的Oncreate()函数中调用getPhoneContacts()函数得到手机通讯录中的联系人信息,然后用继承了BaseAdapter的适配器对联系人的数据信息进行适配。
1.得到手机通讯录联系人信息
getPhoneContacts();
2.对数据进行适配
mListView =(ListView)findViewById(R.id.lv);
myAdapter = new MyListAdapter(this);
mListView.setAdapter(myAdapter);
3.getPhoneContacts()函数的主要代码
// 获取手机联系人
ContentResolver resolver = mContext.getContentResolver();
Cursor phoneCursor = resolver.query(Phone.CONTENT_URI,
PHONES_PROJECTION,null,null,null);
if (phoneCursor != null) {
while (phoneCursor.moveToNext()) {
//得到手机号码
String phoneNumber = phoneCursor.getString
(PHONES_NUMBER_INDEX);
//当手机号码为空的或者为空字段 跳过当前循环
if (TextUtils.isEmpty(phoneNumber))
continue;
//得到联系人名称
String contactName = phoneCursor.getString
(PHONES_DISPLAY_NAME_INDEX);
//得到联系人ID
Long contactid = phoneCursor.getLong(PHONES_CONTACT_ID_INDEX);
//得到联系人头像ID
Long photoid = phoneCursor.getLong(PHONES_PHOTO_ID_INDEX);
//得到联系人头像Bitamp
Bitmap contactPhoto = null;
//photoid 大于0 表示联系人有头像 如果没有给此人设置一个默认的头像
if(photoid > 0 ) {
Uri uri =ContentUris.withAppendedId(ContactsContract.
Contacts.CONTENT_URI,contactid);
InputStream input = ContactsContract.Contacts.openContact
PhotoInputStream(resolver, uri);
contactPhoto = BitmapFactory.decodeStream(input);
}else {
contactPhoto = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
}
mContactsName.add(contactName);
mContactsNumber.add(phoneNumber);
mContactsPhonto.add(contactPhoto);
}
phoneCursor.close();
}
4.对数据进行适配的主要代码
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item, null);
}//拨打电话图标和发送短信图标则直接在布局中加载出来了
message = (ImageButton) convertView.findViewById(R.id.imageButton2);
//绘制联系人名称
title.setText(mContactsName.get(position));
//绘制联系人号码
text.setText(mContactsNumber.get(position));
//绘制联系人头像
iamge.setImageBitmap(mContactsPhonto.get(position));
});
在此时使用的ConvertView这个参数用于之前加载好的布局进行缓存,以便之后可以进行重用,对布局界面起到了了一定的优化效果。
5.对于该号码调用系统拨号功能
Intent intent = new Intent(Intent.ACTION_CALL,Uri.parse("tel:"+ mContactsNumber.get(position)));
startActivity(intent);
6.对于得到的该号码调用系统发短信功能
Uri a=Uri.parse("smsto:"+phoneNumber);
Intent intent = new Intent(Intent.ACTION_SENDTO, a);
intent.putExtra("sms_body", message);
startActivity(intent);
在显示的主界面当中,每一个条目的右侧都有拨打电话和发送短信按钮,所以这两个图标所对应的功能也要在适配器中进行适配。点击该按钮的时候,首先要得到点击按钮的相应联系人的电话,然后对该号码进行打电话,发短信的相关操作,因此,这两个按钮也要设置相关的监听事件。
拨号主界面功能:
对于拨号主界面使用内部类按钮设置一个监听器,然后在Onclick()函数中使用Intent打开系统的拨号界面,关键代码如下两行:
Intent intent= new Intent("android.intent.action.DIAL");
startActivity(intent);
添加联系人功能:
1.添加联系人的函数testInsert()
//得到从界面输入的联系人信息
bt_name=(EditText)findViewById(R.id.name);
bt_number=(EditText)findViewById(R.id.number);
bt_email=(EditText)findViewById(R.id.email);
bt_image=(ImageView)findViewById(R.id.imageView1);
String name=bt_name.getText().toString();
String number=bt_number.getText().toString();
String email=bt_email.getText().toString();
bt_image.setDrawingCacheEnabled(true);
Bitmap image= Bitmap.createBitmap(bt_image.getDrawingCache());
bt_image.setDrawingCacheEnabled(false);
ContentValues values = new ContentValues();
Uri rawContactUri = this.getBaseContext().getContentResolver().
insert(RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rawContactUri);
//往Data表入图像数据
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
ByteArrayOutputStream array = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, array);
values.put(Photo.PHOTO, array.toByteArray()); this.getBaseContext().getContentResolver().insert( android.provider.Con tactsContract.Data.CONTENT_URI, values);
//往 data 表入姓名数据
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
values.put(StructuredName.GIVEN_NAME, name); this.getBaseContext().getContentResolver().insert( android.provider.Con tactsContract.Data.CONTENT_URI, values);
//往 data 表入电话数据
values.clear();
values.put(android.provider.ContactsContract.Contacts.Data.RAW_CONTACT_ ID, rawContactId);
values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
values.put(Phone.NUMBER, number);
values.put(Phone.TYPE, Phone.TYPE_MOBILE); this.getBaseContext().getContentResolver().insert(android.provider.Cont actsContract.Data.CONTENT_URI, values);
//往 data 表入 Email 数据
values.clear();
values.put(android.provider.ContactsContract.Contacts.Data.RAW_CONTACT_ ID, rawContactId);
values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
values.put(Email.DATA,email);
values.put(Email.TYPE, Email.TYPE_WORK); this.getBaseContext().getContentResolver().insert(android.provider.Cont actsContract.Data.CONTENT_URI, values);
}
2.利用相机拍摄照片并在界面显示
// 指定开启系统相机的Action
Intent intent = new Intent(Intent.ACTION_PICK, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(intent, 1);
//选择联系人头像并在页面显示
if(requestCode == 0&& resultCode == RESULT_OK){
Bundle extras = data.getExtras();
Bitmap photo= (Bitmap) extras.get("data");
doCropPhoto(photo);
bt_image.setImageBitmap(photo);
}
//doCropPhoto()函数
protected void doCropPhoto(Bitmap data){
Intent intent = getCropImageIntent(data);
startActivityForResult(intent, 0);
}
//getCropImageIntent()函数
public static Intent getCropImageIntent(Bitmap data) {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setType("image/*");
intent.putExtra("data", data);
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", 64);
intent.putExtra("outputY", 64);
intent.putExtra("return-data", true);
return intent;
}
3.从图库选择头像并在界面显示
//打开系统的图库
Intent intent2 = new Intent(Intent.ACTION_PICK, null); intent2.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); startActivityForResult(intent2, 1);
//选择联系人头像并在页面显示
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// TODO Auto-generated method stub
bt_image= (ImageView)findViewById(R.id.imageView1);
if (requestCode == 1 && resultCode == RESULT_OK) {
if (data != null) {
bt_image.setImageURI(data.getData());
}
}
super.onActivityResult(requestCode, resultCode, data);
}
修改联系人功能:
1.获取信息并显示在界面中
Intent intent=getIntent();
final String name = intent.getStringExtra("name");
String number = intent.getStringExtra("number");
String email = intent.getStringExtra("email");
Bitmap photo = intent.getParcelableExtra("photo");
bt_name = (EditText)findViewById(R.id.name);
bt_number = (EditText)findViewById(R.id.number);
bt_email= (EditText)findViewById(R.id.email);
bt_image=(ImageView)findViewById(R.id.imageView1);
bt_name.setText(name);
bt_number.setText(number);
bt_email.setText(email);
bt_image.setImageBitmap(photo);
2.对联系人的信息进行修改
testDelete(name);
testInsert();
Intent intent2=new Intent(mContext,MainActivity.class);
startActivity(intent2);
权限问题:
1.读取数据库操作要在Mainifeast.xml中添加如下权限:
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
2. 读取打电话,发短信操作要在Mainifeast.xml中添加如下权限:
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.SEND_SMS" />
四.心得体会
1) 遇到的问题以及解决办法
1.setOnItemClickListener点击事件点击无效
原因:当布局文件中存在Button或者ImageButtond等控件时,点击事件的焦点会由这些控件获得,所以点击Item无效。
解决办法:在ImageButton或Button控件中添加android:focusable="false"属性,并将android:descendantFocusability="blocksDescendants"属性添加给ImageButton或者Button控件。
2.符号没有错误却报错Synax error on token’’’
原因:在内部类的Onclick()方法中不可以直接创建函数。
解决办法:在该内部类外面定义该函数,然后在内部类的Onclick()方法中调用该函数即可。
3.android.content.ActivityNotFoundException:.....
原因:组件Activity没有在AndroidMainifeast.xml中注册。
解决方法:在AndroidMainifeast.xml注册该组件android:name="com.itcast.
mycontacts.MainActivity"
注意:名字一定要完全正确,一开始我就因为名字少打了个字母,然后导致一直报错。
4.DDMS/File Explorer/mnt/sdcard目录打不开
原因:该模拟器没有为SD卡分配内存
解决:重新建一个模拟器,在SD card/size选项中分配一定内存(256M)
5.头像在ListView中显示为黑色的背景
原因:图片存储在数据库中的格式不对。当调用bitmap.compress(Com
pressFormat.JPEG, 100, fos)保存图片时发现图片背景为黑色。
解决:改成用png保存就可以了,bitmap.compress(CompressForma.PNG, 100, fos)
2) 小结
通过这次课程设计,我对本学期所学的知识点有了更进一步的了解和认识。UI布局,Activity组件以及如何在Activity中使用Intent,用SQLite对数据进行存储,如何对ListView进行适配,如何用ContentResolver获取系统数据库中的数据等等,对于这些知识,我得到了进一步的巩固。在这次课程设计当中,我最大的收获是在遇到问题的时候,我变得越来越冷静,能够重新理清思路,再一个个排除可能出错的地方,最后找到错误。
END