写在文章开始之前
老规矩,介绍之前先上图片。
可以看到,效果还是不错的哈,因为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交流