效果图如下
千山万水总是情,别出bug行不行。经过半天的写程序,以及一天的找bug(写出bug全怪自己对线程和游标写的少,嗯,,,后面再说) ,历经磨难,终于实现了这个功能。
进入主题,简单介绍下实现的功能。首先是一个主界面(黑名单管理),点击添加出现自定义Dialog,之后可以选择拦截类型并且添加到SQLite数据库中,对数据进行增删改查。
步骤一:主界面布局
主界面是由TextView、Button、ListView控件来进行xml布局的。我这里用的相对布局,布局就简单略过。
步骤二:对添加按钮进行事件处理
这是我的BlackNumberActivity类,一定要继承 Activity类,里面由三个方法,别急,一个个调用
public class BlackNumberActivity extends Activity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_blacknumber);
//初始化
initUI();
//初始化数据
initData();
//对点击添加按钮事件的处理
event();
}
}
接下来要对添加按钮进行事件处理,点击添加,出现如下界面。原生控件不够用怎么办?当然是自定义控件了。如下就是一个控件自定义的Dialog控件。好,那么问题来了,怎么自定义?
1.先把上面布局写在xml文件中,如下图1,这里用了TextView、EditText、RadioGroup、Button控件进行了xml布局。也是用的相对布局。RadioGroup里由三个RadioButton居中水平排列;为了让“确认”“取消”两个Button宽度相等,我把这两个Button放在了LinearLayout(线性布局)中,因为只有线性布局才能设置权重;之后再把LinearLayout放在相对布局中。
2.xml布局完成,接下来点击添加按钮弹出Dialog,首先先初始化UI,我把这个UI都声明为成员变量
private Button bt_add;
private ListView lv_blacknumber;
private void initUI() {
bt_add = findViewById(R.id.bt_add);
lv_blacknumber = findViewById(R.id.lv_blacknumber);
}
接下来,对添加按钮事件监听,监听事件在event()方法中,我在这里先对Dialog上的控件进行了初始化,后续会用到。这样添加按钮的处理就完成了。
bt_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//创建对话框对象
AlertDialog.Builder builder=new AlertDialog.Builder(this);
//设置自定义对话框
final AlertDialog dialog=builder.create();
//点击对话框以外不会退出,但返回会退出
dialog.setCanceledOnTouchOutside(false);
//加载xml布局
View view=View.inflate(this,R.layout.dialog_add_blacknumber,null);
//初始化控件
et_phone = view.findViewById(R.id.et_phone);
RadioGroup rg_group= view.findViewById(R.id.rg_group);
Button bt_submit=view.findViewById(R.id.bt_submit);
Button bt_cancel=view.findViewById(R.id.bt_cancel);
//将View对象加载到dialog上
dialog.setView(view);
//显示dialog
dialog.show();
}
});
步骤三:创建数据库类
1.添加的数据要添加到SQLite数据库,写一个继承SQLiteOpenHelper的类,重写onCreate()、onUpgrade()两个方法,并且进行有参构造。onUpgrade()实在数据库更新时用的,由于其父类是抽象的,所以即使数据库不更新,也得重写这个方法。 如下:
public class BlackNumberOpenHelper extends SQLiteOpenHelper {
public BlackNumberOpenHelper(Context context) {
// 数据库辅助类的构造方法,当数据库存在,则直接返回,没有则创建数据库,并调用onCreate()初始化数据库
//上下文,数据库名称,游标工厂对象,当前数据库版本号;只传上下文环境参数,其余参数固定写死
super(context,"blacknumber.db",null,1);
}
/**
* 数据库第一次创建的时候调用,在此方法中创建数据表
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
//创建数据库中的表(SQL语句)
db.execSQL("create table blacknumber(_id integer primary key autoincrement ,phone varchar(20),mode varchar(5));");
}
/**
* 当前数据库的版本号升级时调用的方法,
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2.写一个实现数据增删改查的类;
2.1把BlackNumberOpenHelper声明为成员变量; 有参构造去创建上面创建数据库的BlackNumberOpenHelper类的对象。
private BlackNumberOpenHelper blackNumberOpenHelper;
public BlackNumberDao(Context context){
blackNumberOpenHelper=new BlackNumberOpenHelper(context);
}
2.2写添加数据的方法
/**
* 添加一个条目
* @param phone 拦截的电话号
* @param mode 拦截的类型(短信 电话 所有)
*/
public void insert(String phone,String mode){
//开启数据库,准备做写入操作
SQLiteDatabase db=blackNumberOpenHelper.getWritableDatabase();
//创建ContentValues对象封装键值对
ContentValues values=new ContentValues();
//要插入的字段名和字段值
values.put("phone",phone);
values.put("mode",mode);
//插入数据(表名,字段没有值时把字段维护成null,内容值)
db.insert("blacknumber",null,values);
//关闭数据库
db.close();
}
2.3写删除数据的方法
/**
* 从数据库删除一条数据
* @param phone 删除的电话号码
*/
public void delete(String phone){
//开启数据库,
SQLiteDatabase db=blackNumberOpenHelper.getWritableDatabase();
//插入数据(表名,要删除的字段,传入要删除的内容具体值 )
db.delete("blacknumber","phone=?",new String[]{phone});
//关闭数据库
db.close();
}
2.4写修改数据的方法
/**
* 根据电话号码,修改拦截模式
* @param phone 更新拦截模式的电话号码
* @param mode 要更新的模式
*/
public void update(String phone,String mode){
//开启数据库,
SQLiteDatabase db=blackNumberOpenHelper.getWritableDatabase();
//创建ContentValues对象封装键值对
ContentValues values=new ContentValues();
//要修改的字段名和字段值
values.put("mode",mode);
//修改数据(表名,要修改的字段名和字段值,根据字段名更新,根据字段名的具体值来更新)
db.update("blacknumber",values,"phone=?",new String[]{phone});
//关闭数据库
db.close();
}
2.5.写查询所有数据的方法(重点来了,难点来了),由于后面我要查询到数据库所有数据并且填充到LIstView中,所以把phone和mode封装到一个类中(代码如下),并声明set,get方法以及重写toString方法,再将这个类对象添加到集合中。这样一来,后面我填充LIstView时,就可以直接从集合中拿到数据进行填充了。
public class BlackNumberInfo {
private String phone;
private String mode;
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getMode() {
return mode;
}
public void setMode(String mode) {
this.mode = mode;
}
@Override
public String toString() {
return "BlackNumberInfo{" + "phone='" + phone + '\'' + ", mode='" + mode + '\'' + '}';
}
}
/**
* 查询所有号码
*/
public List findAll(){
//开启数据库,
SQLiteDatabase db=blackNumberOpenHelper.getWritableDatabase();
//查询所有号码(表名,查询号码和类型,查询条件没有--null,为了后添加的在ListView上面所以倒叙排序id)
Cursor cursor=db.query("blacknumber",new String[]{"phone","mode"},null,null,null,null,"_id desc");
List blackNumberList=new ArrayList();
//如果游标能往下移动
while (cursor.moveToNext()){
BlackNumberInfo blackNumberInfo=new BlackNumberInfo();
//游标没向下移动一次,就获取索引为0,索引为1的字段(也就是phone字段和mode字段)赋值给BlackNumberInfo对象
blackNumberInfo.setPhone(cursor.getString(0));
blackNumberInfo.setMode(cursor.getString(1));
//把BlackNumberInfo添加到集合中
blackNumberList.add(blackNumberInfo);
}
cursor.close();
//关闭数据库
db.close();
return blackNumberList;
}
为了不让数据库插入相同的数据,所以我再写一个方法,传入输入的手机号,来校验数据是否存在过该手机号,返回值为Boolean类型
//查找数据库中的一条号码,(其返回值用来判断数据库是否存在该号码)
public boolean find(String phone){
//默认没有该数据
boolean result=false;
//开启数据库,
SQLiteDatabase db=blackNumberOpenHelper.getWritableDatabase();
//查询所有号码(表名,查询号码,查询条件没有--null)
Cursor cursor=db.query("blacknumber",new String[]{"phone"},null,null,null,null,null);
//如果游标能往下移动
while (cursor.moveToNext()){
//遍历Cursor对象,并且跟传入的phone进行比较,如果相同就返回true,说明数据库存在该数据
if(phone.equals(cursor.getString(0))){
result=true;
}
}
/*
一定要关闭游标,回收游标对象
*/
cursor.close();
return result;
}
步骤四:对Dialog上控件的监听事件处理。
1。先对RadioGroup控件进行事件处理。那么问题来了,我对这个RadioGroup监听是干什么用的,就是选择一种状态而已。其实,这里对RadioGroup状态监听,是为了下面把拦截类型写入ListView中而做的准备。这里我定义了个String类型的成员变量mode,用来记录RadioGroup是什么状态,以便于确定写入ListView中什么拦截类型。
//监听RadioGroup中选中条目的切换过程
rg_group.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch(checkedId){
case R.id.rb_sms:
//拦截短信,为" 短信"
mode = "短信";
break;
case R.id.rb_phone:
//拦截电话 "电话""
mode= "电话";
break;
case R.id.rb_all:
//拦截所有 如果选择短信,令mode值"所有"
mode= "所有";
break;
}
}
});
2.对确认和取消按监听处理
//对确认按钮监听处理
bt_submit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//1.获取输入框中的电话号码
String phone=et_phone.getText().toString();
if(TextUtils.isEmpty(phone)){
//如果电话为空,打印吐司
ToastUtil.show(BlackNumberActivity.this,"拦截电话号不能为空",0);
}else {
if(mDao.find(phone)){
ToastUtil.show(BlackNumberActivity.this,"已经存在该号码,不能插入",0);
}else {
//2.把电话号插入数据库
mDao.insert(phone,mode);
//3 插入完之后,数据库中多了条数据,但是并没有再次把数据读到集合中,集合还是原来的集合
//所以让数据库和集合保持一致(手动向集合中插入这条数据),由于集合泛型是BlackNumberInfo,所以把数据转换成BlackNumberInfo对象
BlackNumberInfo blackNumberInfo=new BlackNumberInfo();
blackNumberInfo.setPhone(phone);
blackNumberInfo.setMode(mode);
//4.把BlackNumberInfo对象插入集合最顶部(最顶部,索引为0)
blackNumberList.add(0,blackNumberInfo);
//5.通知数据适配器刷新(集合数据有改变)
if(myAdaper!=null){
myAdaper.notifyDataSetChanged();
}
//打印添加成功吐司
ToastUtil.show(getApplication(),"添加成功",0);
dialog.dismiss();
}
}
}
});
//对取消按钮监听
bt_cancel.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
步骤六:填充LIst View
接下来,先查询到数据库的所有数据才能填充,由于查询数据库是耗时操作,所以必须放在线程中进行(线程一定一定要记得.Start,我就是因为没写所以找了大半天的Bug),用到了线程,一定会用到消息机制(因为多线程不能通信)。查询到所有的数据任意发送个消息告诉主线程可以使用包含数据的集合了
//1.获取操作数据库对象
mDao=new BlackNumberDao(this);
//获取数据库中所有的电话号码,由于查询数据可能多,是耗时操作,所以用线程
new Thread(){
@Override
public void run() {
//2.查询所有数据操作
blackNumberList = mDao.findAll();
//3.通过消息机制告诉主线程可以使用包含数据的集合
handler.sendEmptyMessage(0);
};
}.start();
主线程接收到消息后,设置ListView适配器。MyAdaper一定要继承BaseAdaper。swich里传入mode,因为上文已经对RadioGroup进行了监听,加入case到的是字符串"短信",就设置item的文本对应的内容。并且对删除数据监听,调用数据库delete方法。
class MyAdaper extends BaseAdapter {
@Override
//1.ListView的长度,即集合长度
public int getCount() {
return blackNumberList.size();
}
//getItem(int position)和getItemId(int position)也必须重写(因为这是是BaseAdapter中的抽象方法),在调用ListView的响应方法的时候才会被调用到,这里不影响布局
@Override
public Object getItem(int position) {
//根据索引获取当前集合里的对象
return blackNumberList.get(position);
}
@Override
public long getItemId(int position) {
//返回索引值
return position;
}
//根据长度绘制item
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
//把布局文件转换成View对象
//View.inflate加载xml布局,getApplicationContext() 上下文环境
View view=View.inflate(getApplicationContext(),R.layout.listview_blacknumber_item,null);
//初始化
TextView tv_phone=view.findViewById(R.id.tv_phone);
TextView tv_mode=view.findViewById(R.id.tv_mode);
ImageView iv_delete=view.findViewById(R.id.iv_delete);
//根据索引获取集合里的对象,再获取电话号码(即获取到集合中的BlackNumberInfo对象,再调用BlackNumberInfo的getPhone())
tv_phone.setText(blackNumberList.get(position).getPhone());
//获取到拦截模式并转换成int类型
mode=(blackNumberList.get(position).getMode());
switch (mode){
case "短信":
tv_mode.setText("拦截短信");
break;
case "电话":
tv_mode.setText("拦截电话");
break;
case "所有":
tv_mode.setText("拦截所有");
break;
}
//对删除事件的处理
iv_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//1.数据库中删除,根据索引获取当前集合里的对象,即BlackNumberInfo对象,再调用getPhone()
mDao.delete(blackNumberList.get(position).getPhone());
//2.集合中删除(因为数据库中的数据都添加在集合中),再通知数据适配器刷新
//根据索引删除集合元素
blackNumberList.remove(position);
//通知数据适配器刷新
if(myAdaper!=null){
myAdaper.notifyDataSetChanged();
}
//打印删除成功吐司
ToastUtil.show(getApplication(),"删除成功",0);
}
});
return view;
}
}
好了,大功告成。具体是这么几个过程。
源码地址:https://github.com/qiudonghhh/MyAndroidProjects