CS案例之Android手机的排课系统
当前位置:以往案例 > >CS案例之Android手机的排课系统
2017-11-15

同学们好,

本学期的大项目是需要同学们实现一款基于Android手机的排课系统。涉及到手机端的逻辑和服务器端的逻辑。简要要求如下:

每个家长对应的孩子会选择若干课外课程,每个课程有若干助教选择。每个孩子和助教的时间安排和地点安排都各不相同。你们要实现的系统,需要综合考虑学生、助教的时间安排,合理安排课程。安排的结果一定不允许违背每个学生和助教的时间和地点限定。这个项目需要你们从界面设计,调度逻辑等方面入手。

分组:

1~3个同学自愿分组,选出组长找老师登记。

具体分数占比为:

完成基本功能 50分(包括学生、助教设定时间、地点;排课合理;可以演示且无明显bug)

代码整洁,系统运行高效20分

实验报告 30分(需要详细展示设计细节,和排课算法设计)

Why this app matters?

Most preschool and primary school kids in China are attending various

playgroups and interest classes besides their schools. Many of such extracurricular classes are offered by private tutors, e.g., piano tutors, Mandarin/Cantonese teachers, etc. Nowadays parents and tutors are commonly communicating via WhatsApp or WeChat for class schedule, group formation, etc. A tutor or a parent often needs to handle multiple group chats for different classes, manually extracts useful class information, sets calendar/reminder accordingly, etc. In this project, we seek to design a dedicated mobile app for efficient class scheduling between private tutors and parents. The mobile app has a parent view and a tutor view. In the tutor view, a tutor can easily coordinate with different groups of parents for class schedule, venue booking, etc. In the parent view, a parent can easily manage classes his/her kid is attending with alarm/reminder functions, and to see information of tutors (e.g., available time slots) for easy class booking. The app can connect with WeChat or WhatsApp for further information exchange of respective groups. There is an interesting multi-way matching problem to solve, for implementing the system. To implement the server side, students are encouraged to explore rich functionalities provided by a modern cloudplatform.

一. 课程设计目的:

该课程设计的目的是为了让我们更加熟悉 Android 应用开发流程,从前端到后端的对接,组件的搭建,和 UI 布局等等,熟悉掌握 Android 开发所需知识。

二. 需求分析

2.1 系统需求规定

1.背景:
手机 APP 如今已经成了人类的必需品之一,安卓和 IOS 作为市场的两大势力,安卓用户的数量也是占了有一半以上的,在中国大多数幼儿园和小学的孩子们参加各种活动和兴趣班,除了他们的学校。许多课外课程是由私人导师提供的, 例如,钢琴导师,普通话/广东话教师等,这些课程应该都需要一个排课系统去方便他们排出一个上课的时间安排表,根据助教和孩子的日程安排,所以这个系统是为了方便孩子和助教直接的排课。

2.总体功能模块描述
下面表 1 列出了本系统各模块功能需求概要描述,说明如下。

模块

功能描述

登录模块

进入登录界面,在界面中输入帐号与密码,选择身份类型,(首次使用用户可点击注册进行新用户注册),输入正确后登入客

户端。

录入安排模块

学生和助教都可以根据自己的需求添加自己的课程安排(包括课程选择,地点选 择,日期选择,时间选择,星期几选择等

等)

地点模块

用户可以在系统内添加上课地点,添加完后可在首页查看到地点列表并且可以进行

删除操作

课程模块

助教可以在系统内进行课程的添加,并且在系统的首页界面内进行查看已经添加的

课程

课表生成模块

在系统的首页点击生成课表,系统会自动

匹配孩子和助教的安排,如果匹配成功会生成一个课表显示在首页。

退出模块

点击退出按钮,退出个人登录。

2、各功能模块的详细需求描述

下面系列表 2—7 列出了对本系统各模块功能需求,具体的说明如下。

功能模块

登录模块

输入

登录界面中输入个人帐号及密码,选择用户

类型

处理

后端接口验证帐号密码是否正确

展现

成功后进入系统首页

功能模块

录入安排模块

输入

录入安排界面中,按要求完成选择各项输

入,开始日期不能小于今天且要小于结束日期,开始时间要小于结束时间

处理

后端接口验证数据正确后写入数据库

展现

成功添加后跳转到首页

功能模块

地点模块

输入

在添加地点的界面中,输入一个实际的地点

名称点击提交即可

处理

后端接口接收到数据后保存到数据库

展现

成功后清空添加地点的界面的输入框

功能模块

课程模块

输入

在添加课程的界面中,输入一个实际的课程

名称点击提交即可

处理

后端接口接收到数据后保存到数据库

展现

成功后清空添加课程的界面的输入框

功能模块

课表生成模块

点击

点击生成课表按钮

处理

后端自动匹配孩子和助教的课程并生成一

个课程

展现

系统首页显示属于该孩子或助教的课表安



功能模块

退出模块

点击

点击首页的退出登录按钮

处理

后端清除用户的 token

展现

成功后跳转回系统的登录界面

最终实现:

QQ截图20180702125847.jpgQQ截图20180702125854.jpg1.jpg

Main code:





DataBase.java

package cn.edu.zust.zhouxuhang;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class DataBase extends SQLiteOpenHelper{
private final static String DB_NAME="myBase";
private final static String[] TB_NAME={"Mon","Tue","Wed","Thur","Fri","Sat","Sun"};
public  final static String ID="_id";
public final static String CLASS="classes";
public final static String LOCA="location";
public final static String TEACHER="teacher";
public final static String ZHOUSHU="zhoushu";
public final static String JIESHU="jieshu";
public final static String TIME1="time1";
public final static String TIME2="time2";
public final static String WHICH="which";
public DataBase(Context context){
super(context,DB_NAME,null,1);
}
@Override
public void onCreate(SQLiteDatabase db) {
for(int i=0;i<7;i++){ String sql="CREATE TABLE "+TB_NAME[i]+" (_id INTEGER primary key autoincrement,classes varchar(70),location varchar(70)," + "teacher varchar(70),zhoushu varchar(70),time1 varchar(70),time2 varchar(70),jieshu varchar(70),which varchar(70))"; db.execSQL(sql); } } @Override public void onUpgrade(SQLiteDatabase db, int oleVersion, int newVersion) { for(int i=0;i<7;i++){ String sql="DROP TABLE IF EXISTS "+TB_NAME[i]; db.execSQL(sql); } onCreate(db); } public Cursor select(int i){ SQLiteDatabase db=DataBase.this.getReadableDatabase(); Cursor cursor=db.query(TB_NAME[i],null,null,null,null,null,null); return cursor; } public long insert(int i,String cla,String loca,String tea,String zhou,String jie,String time1,String time2,String which){ SQLiteDatabase db=DataBase.this.getWritableDatabase(); ContentValues cv=new ContentValues(); cv.put(CLASS,cla); cv.put(LOCA, loca); cv.put(TEACHER,tea); cv.put(ZHOUSHU,zhou); cv.put(JIESHU,jie); cv.put(TIME1,time1); cv.put(TIME2,time2); cv.put(WHICH,which); long row=db.insert(TB_NAME[i],null,cv); return row; } public void update(int i,int _id,String cla,String loca,String tea,String zhou,String jie,String time1,String time2,String which){ SQLiteDatabase db=DataBase.this.getWritableDatabase(); String where="_id = ?"; String[] whereValues={Integer.toString(_id)}; ContentValues cv=new ContentValues(); if(!cla.equals("")) cv.put(CLASS,cla); if(!loca.equals("")) cv.put(LOCA, loca); if(!tea.equals("")) cv.put(TEACHER,tea); if(!zhou.equals("")) cv.put(ZHOUSHU,zhou); if(!jie.equals("")) cv.put(JIESHU,jie); if(!time1.equals("")) cv.put(TIME1,time1); if(!time2.equals("")) cv.put(TIME2,time2); if(!which.equals("")) cv.put(WHICH,which); db.update(TB_NAME[i], cv, where, whereValues); } public void deleteData(int i,int _id){ SQLiteDatabase db=DataBase.this.getWritableDatabase(); String where="_id = ?"; String[] whereValues={Integer.toString(_id)}; ContentValues cv=new ContentValues(); cv.put("classes",""); cv.put("location",""); cv.put("teacher",""); cv.put("zhoushu",""); cv.put("jieshu",""); cv.put("time1",""); cv.put("time2",""); cv.put("which",""); db.update(TB_NAME[i], cv, where, whereValues); } public void delete(int i,int _id){ SQLiteDatabase db=this.getWritableDatabase(); String where="_id = ?"; String[] whereValues={Integer.toString(_id)}; db.delete(TB_NAME[i], where, whereValues); } public void createTable(int j){ for(int i=1;i<=12;i++) insert(j,"", "", "","","","","",""); } }
LauncherReceiver.java

package cn.edu.zust.zhouxuhang;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
public class LauncherReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context arg0, Intent arg1) {
//LauncherReceiver用来监听系统开机事件,如果之前已经设置了启动SetQuietService,即quiet_status为true,
//而后来因为某种原因关机了,则开机启动SetQuietService
SharedPreferences preferences = arg0.getSharedPreferences("switch", Context.MODE_MULTI_PROCESS);
Boolean quiet_status = preferences.getBoolean("switch_quiet", false);
Boolean remind_status = preferences.getBoolean("switch_remind", false);
Intent quiet_intent = new Intent(arg0,SetQuietService.class);
Intent remind_intent = new Intent(arg0,RemindReceiver.class);
PendingIntent pi_remind = PendingIntent.getBroadcast(arg0, 0, remind_intent, 0);
AlarmManager am = (AlarmManager)arg0.getSystemService(Service.ALARM_SERVICE);
if(quiet_status){
arg0.startService(quiet_intent);
}
if(remind_status){
am.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), 60000, pi_remind);
}else{
am.cancel(pi_remind);
}
}
}
LoadActivity.java

package cn.edu.zust.zhouxuhang;
import cn.edu.zust.zhouxuhang.MainActivity;
import cn.edu.zust.zhouxuhang.R;
import android.app.Activity;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.Handler;
import android.view.WindowManager;
public class LoadActivity extends Activity {

//time for picture display
private static final int LOAD_DISPLAY_TIME = 3000;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

getWindow().setFormat(PixelFormat.RGBA_8888);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DITHER);
setContentView(R.layout.activity_load);

new Handler().postDelayed(new Runnable() {
public void run() {
//Go to main activity, and finish load activity
Intent mainIntent = new Intent(LoadActivity.this, MainActivity.class);
LoadActivity.this.startActivity(mainIntent);
LoadActivity.this.finish();
}
}, LOAD_DISPLAY_TIME);
}
}
MainActivity.java

package cn.edu.zust.zhouxuhang;
import cn.edu.zust.zhouxuhang.SetActivity;
import android.media.AudioManager;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Service;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.support.v4.widget.SimpleCursorAdapter;
import android.util.Log;
import android.view.GestureDetector;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;
public class MainActivity extends Activity {

public ListView list[] = new ListView[7];
private TabHost tabs   = null;
private TextView exitButton = null;
private TextView setButton = null;
public static DataBase db;
public Cursor[] cursor=new Cursor[7];
public SimpleCursorAdapter adapter;
private SharedPreferences pre;
//定义手势检测器实例
private GestureDetector detector = null;
//定义手势动作两点之间的最小距离
private final int FLIP_DISTANCE = 200;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//将该activity加入到MyApplication对象实例容器中
MyApplication.getInstance().addActivity(this);
db=new DataBase(MainActivity.this);
pre=getSharedPreferences("firstStart",Context.MODE_PRIVATE);
/*
* 判断程序是否第一次运行,如果是创建数据库表
*/
if(pre.getBoolean("firstStart", true)){
SingleInstance.createTable();
(pre.edit()).putBoolean("firstStart",false).commit();
//finish();
}
exitButton = (TextView)findViewById(R.id.exitButton);
setButton = (TextView)findViewById(R.id.setButton);
list[0] = (ListView)findViewById(R.id.list0);
list[1] = (ListView)findViewById(R.id.list1);
list[2] = (ListView)findViewById(R.id.list2);
list[3] = (ListView)findViewById(R.id.list3);
list[4] = (ListView)findViewById(R.id.list4);
list[5] = (ListView)findViewById(R.id.list5);
list[6] = (ListView)findViewById(R.id.list6);
tabs  = (TabHost)findViewById(R.id.tabhost);
//创建手势检测器
detector = new GestureDetector(this, new DetectorGestureListener());

//在配置任何的TabSpec之前,必须在TabHost上调用该方法
tabs.setup();
//为主界面注册七个选项卡
TabHost.TabSpec  spec = null;
addCard(spec,"tag1",R.id.list0,"日");
addCard(spec,"tag2",R.id.list1,"一");
addCard(spec,"tag3",R.id.list2,"二");
addCard(spec,"tag4",R.id.list3,"三");
addCard(spec,"tag5",R.id.list4,"四");
addCard(spec,"tag6",R.id.list5,"五");
addCard(spec,"tag7",R.id.list6,"六");
//修改tabHost选项卡中的字体的颜色
TabWidget tabWidget = tabs.getTabWidget();
for(int i=0;i FLIP_DISTANCE){
if(i FLIP_DISTANCE){
if(i>0)
tabs.setCurrentTab(i-1);
return true;
}
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX, float distanceY) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
}
//覆写Activity中的onTouchEvent方法,将该Activity上的触碰事件交给GestureDetector处理
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
//设置菜单按钮
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
//当点击菜单中的“退出”键时,弹出提示是否退出的对话框
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//创建AlertDialog.Builder对象,该对象是AlterDialog的创建器,AlterDialog用来创建弹出对话框
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
if(item.getItemId() == R.id.menu_exit){
exit(builder);
return true;
}
if(item.getItemId() == R.id.menu_settings){
Intent intent = new Intent(MainActivity.this, SetActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
//子 方法:为主界面添加选项卡
public void addCard(TabHost.TabSpec spec,String tag,int id,String name){
spec = tabs.newTabSpec(tag);
spec.setContent(id);
spec.setIndicator(name);
tabs.addTab(spec);
}
//子方法:用来弹出是否退出程序的对话框,并执行执行是否退出操作
public void exit(AlertDialog.Builder builder){
//为弹出的对话框设置标题和内容
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("退出程序");
builder.setMessage("确定要退出课程表系统吗?");
//设置左边的按钮为“确定”键,并且其绑定监听器,点击后退出
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//退出应用程序,即销毁地所有的activity
MyApplication.getInstance().exitApp();
}
});
//设置右边的按钮为“取消”键,并且其绑定监听器,点击后仍然停留在当前界面
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
//创建并显示弹出的对话框
builder.create().show();
}
/*
* 为每一个list提供数据适配器
*/
@SuppressWarnings("deprecation")
public SimpleCursorAdapter adapter(int i){
return new SimpleCursorAdapter(this, R.layout.list_v2,cursor[i],new String[]{"_id","classes","location",
"teacher","zhoushu"},new int[]{R.id.number,R.id.ltext0,R.id.ltext1,R.id.ltext6,R.id.ltext7} );
}
/*
* 第一次运行时创建数据库表
*/
static class SingleInstance{
static SingleInstance si;
private SingleInstance(){
for(int i=0;i<7;i++){ db.createTable(i); } } static SingleInstance createTable(){ if(si==null) return si=new SingleInstance(); return null; } } }
MyAdapter.java

package cn.edu.zust.zhouxuhang;
import cn.edu.zust.zhouxuhang.R;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.support.v4.widget.SimpleCursorAdapter;
import android.widget.Adapter;
public class MyAdapter {
private Context context;
private MainActivity main;
private Cursor[] cursor=new Cursor[7];
private SimpleCursorAdapter[] adapter;
private SharedPreferences preferences;
public MyAdapter(Context context){
this.context=context;
main=(MainActivity) context;
}
public void test(){
}
}
MyApplication.java

package cn.edu.zust.zhouxuhang;
import java.util.LinkedList;
import java.util.List;
import android.app.Activity;
import android.app.Application;
//MyApplication类用来存储每一个activity,并实现关闭所有activity的操作
public class MyApplication extends Application {
//定义容activity容器
private List activityList = new LinkedList();
private static MyApplication instance;
private MyApplication(){}
//单例设计模式中取得唯一的MyApplication实例
public static MyApplication getInstance(){
if(instance == null)
instance = new MyApplication();
return instance;
}
//添加activity到容器中
public void addActivity(Activity activity){
activityList.add(activity);
}
//遍历所有的activity并finish
public void exitApp(){
for(Activity activity : activityList){
if(activity != null)
activity.finish();
}
System.exit(0);
}
//清空缓存
@Override
public void onLowMemory() {
super.onLowMemory();
System.gc();
}
}
MyDialog.java

package cn.edu.zust.zhouxuhang;
import java.util.Calendar;
import cn.edu.zust.zhouxuhang.R;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TimePicker;
import android.widget.Toast;
public class MyDialog {
private EditText course_name;
private EditText course_address;
private EditText course_teacher;
private EditText course_week;
//private EditText course_time1,course_time2;
private EditText course_count;
private View view;
private Context context;
private LayoutInflater inflater;
private Builder builder;
MyAdapter adapter;
MainActivity main;
String s1="",s2="",s3="",s4="",s5="",s6="",s7="";
public MyDialog(Context context){
this.context=context;
main=(MainActivity) context;
adapter=new MyAdapter(context);
}
/*
* 点击未编辑的课程列表跳出”添加课程“对话框
*/
public void add(final int day,final int n){
//填装对话框的view
inflater=LayoutInflater.from(context);
view=inflater.inflate(R.layout.edit_shedule,null);
findWidgetes();//取部件
final Button course_time1=(Button)view.findViewById(R.id.time1);
final Button course_time2=(Button)view.findViewById(R.id.time2);
//为两个输入时间的按钮绑定监听器
course_time1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TimeSet_Dialog(course_time1);
}
});
course_time2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TimeSet_Dialog(course_time2);
}
});
builder=new AlertDialog.Builder(context)
.setIcon(R.drawable.ic_launcher)
.setTitle("编辑课程信息")
.setView(view)
.setPositiveButton("确认",new OnClickListener(){
@SuppressWarnings("deprecation")
@Override
public void onClick(DialogInterface arg0, int arg1) {
if(!(s1=course_name.getText().toString()).equals("")) s1="课程: "+s1;
if(!(s2=course_address.getText().toString()).equals("")) s2="地点: "+s2;
if(!(s3=course_teacher.getText().toString()).equals("")) s3="老师: "+s3;
if(!(s4=course_week.getText().toString()).equals("")) s4="周数: "+s4;
if(!(s6=course_time1.getText().toString()).equals("")) s6="时间: "+s6;
if(!(s7=course_time2.getText().toString()).equals("")) ;
if((s5=course_count.getText().toString()).equals("")||s1.equals("")) {
Toast.makeText(context, "请正确输入课程及节数!", 3000).show();
return;
}
else {
int i=Integer.parseInt(s5.trim());//i为节数
for(int m=0;mn1){
switch(n2){//更新数据
case 0:
for(int m=0;m
RemindActivity.java


package cn.edu.zust.zhouxuhang;
import cn.edu.zust.zhouxuhang.R;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Service;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.os.Vibrator;
public class RemindActivity extends Activity
{
private Vibrator vibrator;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
SharedPreferences pre = getSharedPreferences("time", Context.MODE_MULTI_PROCESS);
int advance_time = pre.getInt("time_choice", 30);
//获取系统的vibrator服务,并设置手机振动2秒
vibrator = (Vibrator)getSystemService(Service.VIBRATOR_SERVICE);
vibrator.vibrate(2000);
// 创建一个对话框
final AlertDialog.Builder builder= new AlertDialog.Builder(RemindActivity.this);
builder.setIcon(R.drawable.ic_launcher);
builder.setTitle("温馨提示");
builder.setMessage("童鞋,还有" + advance_time + "分钟就要上课了哦!");
builder.setPositiveButton(
"确定" ,
new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog , int which)
{
// 结束该Activity
RemindActivity.this.finish();
}
}
)
.show();
}
@Override
public void onBackPressed() {
super.onBackPressed();
// 结束该Activity
RemindActivity.this.finish();
}
}

RemindReceiver.java


package cn.edu.zust.zhouxuhang;
import java.util.Calendar;
import java.util.Date;
import cn.edu.zust.zhouxuhang.RemindActivity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
public class RemindReceiver extends BroadcastReceiver {
//定义保存每张表数据的cursor集合
Cursor[] cursor = new Cursor[7];
//保存时间,temp[day][row][hm]表示第day+

在线提交订单