一些改动
首先,为了方便按条件查询时日期时间的比较,我们在Schedule类以及数据库表中加入了一个long类型的变量与字段,用于保存日期时间,同时也在本地数据库的工具类中做了相应的更改
由于用户的UID传递较麻烦,所以这里新建了一个类继承自Application,添加了一个名为user_id的成员变量,并添加了get set方法
添加日程功能
上次做好了本地数据库的工具类,所以添加功能是比较简单的,只需要
获取信息->判断信息合法性->封装至对象->保存至本地数据库->保存至firebase数据库
首先设计UI
这个UI也是比较简单,把所有需要的信息放上去就OK了,就只有日期时间特别点,需要用按钮来触发选择器,默认为当前时间,拖放控件,修改ID,在string.xml中加入资源文本,修改按钮的OnClick事件,做好后如图
初始化日期时间选择器部分
由于时间选择器和日期选择器差不多,所以这里只展示选择日期的部分
在窗口类中添加几个成员变量-日期选择器,侦听器,保存选择了的日期时间的int变量以及一个用于存储选择的日期时间的Calendar类对象
private DatePickerDialog dateDlg;
private DatePickerDialog.OnDateSetListener dateLis;
private int mYear,mMonth,mDay;
private int mHour,mMin;
private Calendar mCal=Calendar.getInstance();
再定义一个当日期选择了时触发的用于更新UI的函数()
private void OnDateChose(){
tv_date.setText(mYear+"-"+mMonth+"-"+mDay);
}
然后在自己定义的init函数中初始化这些对象
dateLis=new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
mYear=year;
mMonth=month;
mDay=dayOfMonth;
mCal.set(Calendar.YEAR,mYear);
mCal.set(Calendar.MONTH,mMonth);
mCal.set(Calendar.DAY_OF_MONTH,mDay);
mMonth++;
OnDateChose();
}
};
mYear = mCal.get(Calendar.YEAR);
mMonth = mCal.get(Calendar.MONTH);
mDay = mCal.get(Calendar.DAY_OF_MONTH);
mHour= mCal.get(Calendar.HOUR_OF_DAY);
mMin= mCal.get(Calendar.MINUTE);
dateDlg=new DatePickerDialog(this,dateLis,mYear,mMonth,mDay);
...
OnDateChose();
由于保存的日期月份与选择的月份相差1,且mCal变量中以及保存了选择好的日期,所以这里为了显示时正常将mMonth变量值加一
初始化完成后调用一次更新UI用的函数以显示初始值
然后编写选择日期按钮的OnClick函数
public void date(View v){
dateDlg.show();
}
接着编新建日程按钮的OnClick响应函数
public void insert(View v){
String id= RandomUtils.getRandomId();
String date=mYear+"-"+(mMonth+1)+"-"+mDay;
String time=mHour+":"+mMin;
String who=et_who.getText().toString();
String more=et_more.getText().toString();
int level=sp_level.getSelectedItemPosition();
if(who.equals("") || more.equals("")){
UIUtils.makeToast("不能留空",this);
}
Schedule schedule=new Schedule(id,date,time,who,more,null,level,mCal.getTimeInMillis());
ScheduleDbUtils db=new ScheduleDbUtils(this,null);
db.insert(schedule);
cloud(schedule);
}
最后编写函数cloud用于将数据备份至firebase数据库
public void cloud(Schedule schedule){
final AlertDialog dlg=UIUtils.createDialog(getString(R.string.insert_cloud_handling),this);
String uid=((GreenAppApplication)getApplication()).getUid();
FirebaseDatabase db=FirebaseDatabase.getInstance(); db.getReference().child("schedules").child(uid).child(schedule.getId()).setValue(schedule).addOnCompleteListener(new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
dlg.dismiss();
if(task.isSuccessful()){
UIUtils.makeToast(getString(R.string.insert_succeed),InsertScheduleActivity.this);
}else{
UIUtils.makeToast(getString(R.string.insert_failed),InsertScheduleActivity.this);
}
InsertScheduleActivity.this.finish();
}
});
}
编写查询功能
查询主要有两种方式,一种是查询全部,一种是按条件查询
改动工具类
原来的本地数据库工具类的查询功能以及不能满足需求了,需要再定义一个接受whereClause与whereArgs的查询函数并让接受id的查询函数调用那个函数
public List<Schedule> select(String id) {
if(id==null){
return select(null,null);
}else{
String whereClause = "s_id=?";
String[] whereArgs = {id};
return select(whereClause,whereArgs);
}
}
public List<Schedule> select(String whereClause,String[] whereArgs) {
SQLiteDatabase db = getReadableDatabase();
List<Schedule> result = new ArrayList<>();
Cursor cursor = db.query(TABLE_NAME, null, whereClause, whereArgs, null, null, null);
if (cursor.moveToFirst()) {
for (int i = 0; i < cursor.getCount(); i++) {
cursor.move(i);
String id=cursor.getString(0);
String date=cursor.getString(1);
String time=cursor.getString(2);
String who=cursor.getString(3);
int level=cursor.getInt(4);
String more=cursor.getString(5);
long t=cursor.getLong(6);
Schedule schedule=new Schedule(id,date,time,who,more,null,level,t);
result.add(schedule);
}
}
return result;
}
设计UI与实现功能
这里会出现四个Activity(今后可能会用对话框代替部分)
分别是充当菜单作用的SelectSchedulesActivity,
用户输入查询条件的SelectSchedulesConditionActivity,
显示查询结果的SelectSchedulesResultActivity,
用于显示日程详细信息并提供更改与删除操作的SelectSchedulesResultMoreActivity
SelectSchedulesActivity:
由于这只是充当菜单作用,所以UI相对简单
下面两个按钮只是用于跳转至其他Activity,重点是恢复与备份功能(以后可能会移到别处或自动处理)
这里把两个功能分开来做
备份将本地数据同步到云端,就需要获取本地的所有日程,然后挨个上传至服务器(以后可能会加入一个字段来保存日程内容的MD5,如果MD5且ID相同的日程就跳过以减少服务器与客户端的负担)
public void cloud(View v){
str_id=R.string.select_cloud_succeed;
List<Schedule> schedules=db.select(null);
if(schedules==null || schedules.size()==0){
UIUtils.makeToast(getString(str_id),SelectSchedulesActivity.this);
return;
}
FirebaseDatabase fdb=FirebaseDatabase.getInstance();
final AlertDialog dlg=UIUtils.createDialog(getString(R.string.select_cloud_handling),this);
for(final Schedule schedule:schedules){
DatabaseReference ref=fdb.getReference().child("schedules").child(uid).child(schedule.getId());
ref.setValue(schedule).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
str_id=R.string.select_cloud_failed;
}
});
}
dlg.dismiss();
UIUtils.makeToast(getString(str_id),SelectSchedulesActivity.this);
}
恢复功能就是将服务器上的弄到本地数据库里面去
但由于本地数据库并不能覆盖同ID的数据
所以先判断是否存在同ID的,不存在就添加,存在就修改
public void download(View v){
str_id=R.string.select_download_succeed;
FirebaseDatabase fdb=FirebaseDatabase.getInstance();
final AlertDialog dlg=UIUtils.createDialog(getString(R.string.select_download_handling),this);
final DatabaseReference ref=fdb.getReference().child("schedules").child(uid);
ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
if(dataSnapshot.getChildrenCount()==0){
str_id=R.string.select_download_succeed;
}
Iterable<DataSnapshot> children=dataSnapshot.getChildren();
for(DataSnapshot child:children){
String id=child.child("id").getValue(String.class);
List<Schedule> result=db.select(id);
if(result!=null && result.size()!=0){
db.update(child.getValue(Schedule.class),id);
continue;
}
db.insert(child.getValue(Schedule.class));
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
str_id=R.string.select_download_failed;
return;
}
});
dlg.dismiss();
UIUtils.makeToast(getString(str_id),SelectSchedulesActivity.this);
}
SelectSchedulesConditionActivity:
按条件查询就要考虑输入的空白数据,因为用户要使用这种查询是因为用户并不知晓所需日程的全部内容,所以这里有两种解决方案,一种是判断所输入的是否为空,如果为空那么就让该字段为任意值都可以查询到,即忽略该字段,还有一种是使用户并不用做出任何操作就可以查询到所有,这里显然是后者更方便,由于数据库中保存了日期时间化为long类型后的数据,所以用大于小于运算符就可以做出比较,文本类信息就用LIKE关键字,在所输入的数据的前后加入通配符’%’
这里的默认日期时间就是当时,默认查找当前时间之后的
等级默认是最低的那个,然后条件是大于等于,即任何日程都符合
其他的文本信息默认为空,即任何文本都可以匹配成功
UI也以选择性的控件为主,以引导用户查找
日期时间选择器前面以及介绍过了,重点在于查找部分
public void select(View v){
int time_con=sp_date_time_condition.getSelectedItemPosition();
int level_con=sp_level_condition.getSelectedItemPosition();
int level=sp_level.getSelectedItemPosition();
long time=mCal.getTimeInMillis();
String who=et_who.getText().toString();
String more=et_more.getText().toString();
String operatorTime[]={"<","=",">"};
String operatorLevel[]={">","=","<",">=","<="};
who="%"+who+"%";
more="%"+more+"%";
String whereClause="s_time_long "+operatorTime[time_con]+" ? and s_level"+operatorLevel[level_con]+" ? and s_who like ? and s_more like ?";
String whereArgs[]={String.valueOf(time),String.valueOf(level),who,more};
ScheduleDbUtils db=new ScheduleDbUtils(this,null);
List<Schedule> schedules=db.select(whereClause,whereArgs);
Intent intent=new Intent(this,SelectSchedulesResultActivity.class);
intent.putExtra("schedules", (Serializable) schedules);
startActivity(intent);
}
这样就可以查找出符合条件的日程了
SelectSchedulesResultActivity:
这里的控件也就只有个ListView,用于展示查询结果
这里需要一个适配器
新建一个类MyAdapter继承自BaseAdapter,添加构造函数获取必须的数据
class MyAdapter extends BaseAdapter{
private List<Schedule> schedules;
private boolean isListNull=false;
private Context context;
public MyAdapter(List<Schedule> schedules,boolean isListNull,Context context){
this.schedules=schedules;
this.isListNull=isListNull;
this.context=context;
}
...
这里在res目录下新建一个view
这里只需改动一下getCount函数以及getView函数
@Override
public int getCount() {
if(isListNull){
return 1;
}
return schedules.size();
}
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if(isListNull){
LinearLayout layout=new LinearLayout(context);
TextView tv=new TextView(context);
tv.setText("没有数据");
layout.addView(tv);
return layout;
}
View view=View.inflate(context,R.layout.view_select_result,null);
TextView tv_date,tv_time,tv_who,tv_level,tv_more;
tv_date= (TextView) view.findViewById(R.id.select_result_tv_date);
tv_time= (TextView) view.findViewById(R.id.select_result_tv_time);
tv_who= (TextView) view.findViewById(R.id.select_result_tv_who);
tv_level= (TextView) view.findViewById(R.id.select_result_tv_level);
tv_more= (TextView) view.findViewById(R.id.select_result_tv_more);
Schedule schedule=schedules.get(position);
String date=schedule.getDate();
String time=schedule.getTime();
String who=schedule.getWho();
String level= schedule.levelToString(context);
String more=schedule.getMore();
tv_date.setText(context.getString(R.string.select_result_date)+date);
tv_time.setText(context.getString(R.string.select_result_time)+time);
tv_who.setText(context.getString(R.string.select_result_who)+who);
tv_level.setText(context.getString(R.string.select_result_level)+level);
tv_more.setText(context.getString(R.string.select_result_more)+more);
return view;
}
这里还需要为这个ListView做一个项目点击侦听器
class MyClickListener implements AdapterView.OnItemClickListener{
private Context context;
private List<Schedule> schedules;
public MyClickListener(Context context,List<Schedule> schedules){
this.context=context;
this.schedules=schedules;
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if(schedules.size()==0)
return;
Schedule schedule=schedules.get(position);
Intent intent=new Intent(context,SelectSchedulesResultMoreActivity.class);
intent.putExtra("schedule",schedule);
intent.putExtra("pos",position);
context.startActivity(intent);
}
}
由于More窗口处理完成后需要告知Result窗口,所以这里再定义一个个广播接收者
private BroadcastReceiver receiver=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int what=intent.getIntExtra("what",0);
int pos=intent.getIntExtra("pos",0);
switch(what){
case 0://Delete
schedules.remove(pos);
break;
case 1://Update
Schedule schedule= (Schedule) intent.getSerializableExtra("schedule");
schedules.set(pos,schedule);
break;
}
lv_result.setAdapter(new MyAdapter(schedules,(schedules.size()==0)?true:false,context));
}
};
最后在init函数中设置它们
public void init(){
lv_result= (ListView) findViewById(R.id.select_result_lv_result);
schedules= (List<Schedule>) getIntent().getSerializableExtra("schedules");
boolean isListNull=false;
if(schedules==null || schedules.size()==0){
isListNull=true;
}else{
lv_result.setOnItemClickListener(new MyClickListener(this,schedules));
}
adapter=new MyAdapter(schedules,isListNull,this);
}
在onCreate函数中注册广播接受者
IntentFilter filter = new IntentFilter();
filter.addAction("com.greenapp.note.SCHEDULE_CHANGE");
registerReceiver(receiver,filter);
SelectSchedulesResultMoreActivity:
这个UI和insert的差不多
功能的实现也和insert类似,获取信息,然后本地数据库的update,再设置firebase数据库中的值,就ok了,删除只需删除本地数据库里的值,然后将firebase数据库里的值设置为null就ok了
db.getReference().child("schedules").child(uid).child(schedule.getId()).setValue(null)//删除