写在文章开始之前
老规矩,介绍之前先上图片。

可以看到,效果还是不错的哈,因为java写的代码可以直接放到Android上运行,所以我在android端只是做了一下UI上的数据展示而已,真正的获取课表的代码都是我在前面两篇博客里面扒拉出来的。所以只想了解原理的只需要看看这两篇博客而已。
Android登录界面
登录界面的布局参考了一下这个博客,使用的是相对布局,因为相对布局一个View可以覆盖在另一个View上面,就像图片中的用户名和密码框,最左边的文字就是一个TextView然后下面覆盖了一个EditView,然后设置一下EditView的左内边距就可实现这个效果了,至于EditText的样式,使用的是shape.xml文件,定义一个shape样式。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jay.loginmyschool.MainActivity$PlaceholderFragment" >
<ImageView
android:id="@+id/img"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="35sp"
android:src="@drawable/ic_launcher" />
<EditText
android:id="@+id/id"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_below="@id/img"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="15dp"
android:background="@drawable/layout_username"
android:inputType="number"
android:maxLength="8"
android:paddingLeft="50dip"
android:singleLine="true" />
<TextView
android:id="@+id/tip_id"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignLeft="@id/id"
android:layout_alignTop="@id/id"
android:layout_marginLeft="5dp"
android:gravity="center_vertical"
android:text="@string/user_name"
android:textColor="#999999"
android:textSize="20sp" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_below="@id/id"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:background="@drawable/layout_password"
android:digits="@string/digits"
android:inputType="textPassword"
android:maxLength="16"
android:paddingLeft="50dip" />
<TextView
android:id="@+id/tip_password"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_alignLeft="@id/password"
android:layout_alignTop="@id/password"
android:layout_marginLeft="5dp"
android:gravity="center_vertical"
android:text="@string/password"
android:textColor="#999999"
android:textSize="20sp" />
<LinearLayout
android:id="@+id/radiobutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/password"
android:layout_below="@id/password"
android:layout_marginTop="5dp"
android:orientation="horizontal" >
<CheckBox
android:id="@+id/saveme"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save_me" />
<CheckBox
android:id="@+id/login_self"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_self" />
</LinearLayout>
<Button
android:id="@+id/login"
android:layout_width="match_parent"
android:layout_height="42sp"
android:layout_below="@id/radiobutton"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="15dp"
android:background="@drawable/selector_login"
android:text="@string/login"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
详细的可以查看源码里面的login.xml文件。
Android的登陆操作
由于Android中不允许将耗时操作放在主线程里面,所以登陆以及解析网页的操作都是开启新线程的,由于中间会要求用户输入验证码,所以将获取课表分为两个部分,一个是获取验证码,获取验证码以后等待用户输入,输入完成以后在启动一个线程获取课表,这两个操作分别对应GetBitmap.java和GetTimeTable.java这两个文件,代码大部分都是在前面两篇博客里面复制过来的,就不在赘述了。
至于线程与主线程的通信采用的是handler,在android中,为了防止内存泄露,谷歌推荐我们将handler定义为静态,这里我定义了一个handler,用来传递消息,每一次启动新线程就将这个handler传递过去,然后新线程再通过这个handler通知主线程执行操作。
private static class MyHandler extends Handler {
private WeakReference<MainActivity> ac = null;
public MyHandler(MainActivity ac) {
this.ac = new WeakReference<MainActivity>(ac);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity ac = this.ac.get();
switch (msg.what) {
case GetBitmap.OK: {
ac.DismissProgressDlg();
ac.getLoginButton().setEnabled(true);
ac.setLoginDate((LoginBean) msg.getData().getSerializable(
"logindate"));
ac.showInputCheckDlg((Bitmap) msg.obj);
}
break;
case GetBitmap.ERROR: {
ac.DismissProgressDlg();
ac.getLoginButton().setEnabled(true);
Toast.makeText(ac, "获取验证码失败!", Toast.LENGTH_SHORT).show();
}
break;
case GetTimeTable.LOGIN_ERROR: {
Bundle data = msg.getData();
Toast.makeText(ac, data.getString("error"), Toast.LENGTH_SHORT)
.show();
ac.DismissProgressDlg();
}
break;
case GetTimeTable.LOGIN_OK: {
// Bundle data = msg.getData();
// Toast.makeText(ac, "登录成功", Toast.LENGTH_SHORT).show();
@SuppressWarnings("unchecked")
ArrayList<CourseBean> timeTable = (ArrayList<CourseBean>) msg.obj;
ac.DismissProgressDlg();
Intent intent = new Intent(ac, ShowTimeTable.class);
intent.putExtra("KB", timeTable);
ac.startActivity(intent);
ac.finish();
}
break;
}
}
}
AlertDialog的使用
在登录的过程中,由于网络操作是耗时的,所以我们应该给与用户提示,在图片中可以看到,一旦开始网络请求操作,就会弹出一个等待对话框,这里的等待框使用的是自定义布局的AlertDialog,包括验证码的输入框也是采用的AlertDialog,关于自定义AlertDialog布局,百度已经讲得超级清楚了,如果不想百度,请点这里,如果有错误与遗漏,欢迎大家指正。
两个AlertDialog的布局也是很简单的,等待对话框是一个ProgressBar和一个TextView,验证码输入框是一个EditView加上一个ImageView和Button,大家可以参看源码里面的布局,这里就不在贴出了。
课表的解析
不同学校的课表页面的源码是不同的,不过只要会使用正则表达式,然后耐心点,应该可以搞定。关于正则表达式主要是Pattern类和Matcher类,详细用法可以参考API或者网上其他博客。
/**
* 解析含有课表的网页
*
* @param html
*/
private List<CourseBean> resolveKB(String html) {
Pattern pattern1 = Pattern.compile("width=\"7%\">.*?/td>");
Matcher matcher1 = pattern1.matcher(html);
StringBuffer sb = new StringBuffer();
int begin = 0;
int end = 0;
while (matcher1.find()) {
begin = matcher1.start();
end = matcher1.end();
String str = html.substring(begin + 11, end);
sb.append(str);
}
pattern1 = Pattern.compile("rowspan=\"2\">.*?/td>");
matcher1 = pattern1.matcher(html);
while (matcher1.find()) {
begin = matcher1.start();
end = matcher1.end();
String str = html.substring(begin + 12, end);
sb.append(str);
}
String str = sb.toString().replaceAll("<font.*?</font>", "");
String[] str1 = str.split("</td>");
List<CourseBean> course = new ArrayList<CourseBean>();
for (String s : str1) {
String[] k = s.split("(<br>)+");
for (int i = 0; i < k.length && k.length > 1;) {
String course_name = k[i++];
String course_type = k[i++];
String course_week = k[i++];
String course_teacher = k[i++];
String course_address = k[i++];
CourseBean c = new CourseBean(course_name, course_address,
course_teacher, course_type, course_week);
course.add(c);
}
}
return course;
}
课表的展示
课表的展示使用的是ViewPager,这是supportV4提供的一个控件,一个ViewPager里面包含了7个ListView,在登录成功以后,登录界面会通过intent将解析出来并格式化好的课表发送给课表展示页面,然后由ViewPager负责显示。下面贴出的代码是获取包含指定星期几的ListView,ListViewAdapter 为自己继承自BasedAdapter的适配器类,大家可以参看随后贴出的源码。
/*
* 根据星期几来将所有课程分开,然后节数排序,创建好一个View, 然后返回给ViewPager
*
* 课程时间格式 很明显,按照前面第二个字符可以判断星期几,按照第一个数字可以判断节数大小
*
* 周五第1,2节{第8-9周}]
*/
private View getViewByDate(char date) {
ListView list = new ListView(this);
// 将data中所有课程信息分类
ArrayList<CourseBean> list_data = new ArrayList<CourseBean>();
for (int i = 0; i < data.size(); i++) {
CourseBean course = data.get(i);
Log.v("x", course.getWeek() + "");
if(course.getWeek() == date){
list_data.add(course);
}
}
ListViewAdapter listViewAdapter = new ListViewAdapter(this, list_data);
// 设置适配器
list.setAdapter(listViewAdapter);
// 设置点击时间监听
list.setOnItemClickListener(new ListViewItemClickListener());
return list;
}
点击课表项弹出课程详细信息
这个也是使用的AlertDialog,布局也很简单,一个纵向的ListView,里面包含了5个TextView,TextView左边的图片是使用的下面两个属性,分别是设置TextView左边的图片,以及图片与文字的间距。
android:drawableLeft="@drawable/name"
android:drawablePadding="5dp"
最后再说一句
由于涉及到的知识点太多,而且本人水平有限,只能介绍大概思路,具体的实现可以参看源码,也许其中有很多遗漏甚至错误,希望大家指出。
源码下载:360云盘 访问密码 74d5
博主,360云盘挂了,能用其他方式分享一下吗?
@飘荡的灵魂之尘 不好意思,360云盘关闭没有进行数据备份,所以以前博客里面的Demo都没了,如果有不懂的可以直接QQ交流