0%

通讯录app制作实录

APP介绍

一个普通的通讯录app。有展示联系人的主页面、联系人的详情页、联系人的新增/编辑页面。该app没有读取系统的联系人而是单独使用SQLite数据库来存储app的数据。涉及到的知识点有:SQLite数据库、SharedPreferences文件、Activity的生命周期、Activity的跳转和传值、自定义列表控件的设计和实现、监听器、对话框、JAVA基础知识等。

界面设计

主要有三个页面:app主页、详情页、编辑页。

  • app主页,使用Listview列表显示联系人(头像、姓名、备注),底部有新增联系人按钮
  • 详情页,展示联系人详细信息。
  • 编辑页,编辑联系人信息。

代码实现

导入图片资源

偷懒了,没有把控件样式放到styles.xml文件中😄。

页面

app主页

布局xml

页面设计(layout文件夹内的xml文件)如下:

ListView控件+底部按钮。(这里使用ImageView来充当按钮,按钮也可以使用Button控件使用background属性设置图片)

ListView控件的id为homelist,按钮控件的id为addbtn。(实际上该按钮有新增与删除的功能)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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="match_parent"
android:layout_height="match_parent"
tools:context=".View.MainActivity">

<ListView
android:id="@+id/homelist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="60dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:background="#fff"
android:layout_alignParentBottom="true">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:src="@drawable/add"
android:id="@+id/addbtn"
android:layout_centerInParent="true"/>
</RelativeLayout>
</RelativeLayout>

java

该activity页面的java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.example.addresslist.View;
...
public class MainActivity extends AppCompatActivity implements View.OnClickListener, AdapterView.OnItemLongClickListener, AdapterView.OnItemClickListener{
AL_info_dao al_info_dao;//通讯录 (id、姓名、备注)
AL_tel_dao al_tel_dao;//电话(id、号码)
HomeList_Apdater apdater;
ListView listview;//页面中的ListView控件
ImageView btn;
String btnType="add";//有add和del两种

@Override
protected void onRestart() {//在Activity从其他Activity中返回的时候调用
...//渲染数据
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
...//将从编辑页返回的数据保存到SQLite数据库中
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

listview = (ListView)findViewById(R.id.homelist);
btn =(ImageView)findViewById(R.id.addbtn);
btn.setOnClickListener(this);
listview.setOnItemLongClickListener(this);
listview.setOnItemClickListener(this);
if (al_info_dao == null){
al_info_dao = new AL_info_dao(this);
}
if (al_tel_dao == null){
al_tel_dao = new AL_tel_dao(this);
}
ShowDate(Constant.templateItem);//显示数据到页面上
}

//数据显示到页面上
public void ShowDate(String type){
...
}

@Override
public void onClick(View view) {
...//底部按钮点击事件触发的方法
}

@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
...//Listview中子控件的长按事件触发的方法
}


@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
...//Listview中子控件的点击事件触发的方法
}
}

ShowDate

加载数据到页面(app主页)。根据实参type判断显示数据使用哪个自定义列表文件(有两个布局文件homelist和homelist_check,普通展示数据和删除联系人时展示数据,区别在于后者有一个单选框)。

类Constant是自定义的一个公共类,用来存放经常使用的数据(文本),避免某个地方不小心打错单词的情况。

关于如何将数据渲染到ListView控件中可以回顾一下这篇博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//数据显示到页面上
public void ShowDate(String type){
switch (type){
case Constant.templateItem://普通模板
apdater=new HomeList_Apdater(this,R.layout.homelist,al_info_dao.queryAll());//布局文件、数据集合
btn.setImageResource(R.drawable.add);//按钮
btnType="add";
break;
case Constant.templateItemCheck://带有 单选框的模板(用于删除闹钟数据时)
apdater=new HomeList_Apdater(this,R.layout.homelist_check,al_info_dao.queryAll());
btn.setImageResource(R.drawable.del);//按钮
btnType="del";
break;
}
//数据显示到页面上
listview.setAdapter(apdater);
}

onItemClick

Listview中子控件(某一联系人)的点击事件触发的方法。判断当前主页显示的模式(普通模式、删除联系人模式),普通模式(下方的按钮为新增联系人)时跳转到联系人的详情页。并且将该联系人示例addressList_info传递过去。

1
2
3
4
5
6
7
8
9
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
if (btnType.equals("add")){//删除闹钟按钮 没有显示的时候,才响应
AddressList_info addressList_info = (AddressList_info)apdater.getItem(i);
Intent intent = new Intent(MainActivity.this, Details.class);
intent.putExtra("info", addressList_info);
startActivity(intent);
}
}

onItemLongClick

Listview中子控件(某一联系人)的长按事件触发的方法。

1
2
3
4
5
6
7
8
9
@Override
public boolean onItemLongClick(AdapterView<?> adapterView, View view, int i, long l) {
ShowDate(Constant.templateItemCheck);//重新渲染数据,使用带有单选框的页面布局文件
//清空xml文件(只是临时保存数据,不需要使用上一次在app中添加的数据)
SharedPreferences.Editor edit = getSharedPreferences(Constant.spxmlName_aL_Ids, Context.MODE_PRIVATE).edit();
edit.clear();
edit.commit();
return true;//解决 点击和长按 同时存在的冲突(将false改为true)
}

onClick

底部按钮点击事件触发的方法。主页显示数据的模式有普通模式和删除联系人模式,普通模式下方的按钮为新增联系人,删除联系人模式下方的按钮为删除联系人。

新增联系人,则跳转到编辑页。当从编辑页返回该页面时,执行onActivityResult方法将数据保存到SQLite数据库中。

删除联系人,根据保存在SharedPreferences文件中的id删除相应的联系人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Override
public void onClick(View view) {
if (btnType.equals("add")){
//添加 新联系人
Intent intent = new Intent(MainActivity.this, AddRecord.class);
startActivityForResult(intent,1);
}

//删除
if (btnType.equals("del")){
//ids,要删除的联系人id存储到sharePreference中
SharedPreferences sp = getSharedPreferences(Constant.spxmlName_aL_Ids, Context.MODE_PRIVATE);
final String ids = sp.getString(Constant.spxmlName_aL_key,"");
if (ids.length()>=1){
//对话框 形式提示是否确认删除
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示!");
builder.setMessage("确定要删掉联系人吗?");
builder.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//删除
al_info_dao.delete(ids);//删除 联系人
al_tel_dao.deleteAll(ids);//删除 电话号码
ShowDate(Constant.templateItem);//重新显示数据(使用没有单选框的布局文件)
}
});
builder.setNegativeButton("取消",null);
builder.show();
}else {
//对话框 形式提示是否确认删除
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示!");
builder.setMessage("你还没有选择要删除的联系人呢!");
builder.setPositiveButton("不删了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ShowDate(Constant.templateItem);//重新显示数据(使用没有单选框的布局文件)
}
});
builder.setNegativeButton("继续选择",null);
builder.show();
}
}
}

onActivityResult

将从编辑页返回的数据保存到SQLite数据库中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {//接收 另一个页面返回的数据
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == 2){//resultCode=2,确认。3-取消
Bundle bundle = data.getExtras();//解包
if (bundle != null){
String name = bundle.getString(Constant.beanField_name);
String telephone1 = bundle.getString(Constant.beanField_telephone+"1");
String telephone2 = bundle.getString(Constant.beanField_telephone+"2");
String telephone3 = bundle.getString(Constant.beanField_telephone+"3");
String remarks = bundle.getString(Constant.beanField_remarks);
//加到数据库
if (name.length()>0){//从上一个页面 返回的数据中,name字段必须要有
AddressList_info addressList_info = new AddressList_info(name,remarks);
if (al_info_dao.queryId(addressList_info) == -1){//说明在数据库中 不重名
boolean status = al_info_dao.insert(addressList_info);
Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
if (status == true){//通讯录记录 插入成功
AddressList_tel addressList_tel = new AddressList_tel(al_info_dao.queryId(addressList_info),telephone1);
//保存 电话号码
if (telephone1.length()>0){
al_tel_dao.insert(addressList_tel);
}
if (telephone2.length()>0){
addressList_tel.setTelephone(telephone2);
al_tel_dao.insert(addressList_tel);
}
if (telephone3.length()>0){
addressList_tel.setTelephone(telephone3);
al_tel_dao.insert(addressList_tel);
}
ShowDate(Constant.templateItem);//重新 显示数据到页面上
}
}else{
Toast.makeText(this, "保存失败,存在相同名字", Toast.LENGTH_SHORT).show();
}
}else {
Toast.makeText(this, "保存失败,没有设置名字", Toast.LENGTH_SHORT).show();
}
}
}
if (requestCode==1&&resultCode==3){
Toast.makeText(this, "取消", Toast.LENGTH_SHORT).show();
}
}

onRestart

进入主页时重新加载页面数据。在回调函数onRestart中调用方法ShowDate重新加载页面数据。

1
2
3
4
5
@Override
protected void onRestart() {//在Activity从其他Activity中返回的时候调用
super.onRestart();
ShowDate(Constant.templateItem);//显示数据到页面上
}

详情页

编辑页

SQLite数据库

SP文件

SharedPreferences文件,临时保存数据。

Activity

Activity的生命周期

onRestart、onCreate等

Activity的跳转和传值

Activity之间数据传递

自定义列表控件

自定义列表控件的设计和实现

事件监听器

点击监听器、ListView中子控件的点击/长按监听器(冲突的解决)

ListView需要注意设置:

1
android:descendantFocusability="blocksDescendants"

对话框

普通对话框、带有确认/取消按钮的对话框、加载对话框、时间选择对话框、带输入框的对话框等等。

源码

若图片不能正常显示,请在浏览器中打开

欢迎关注我的其它发布渠道