Android课程表的实现
Android课程表的实现
以往上课之前都要去相册找到本学期的课表截图,不然容易记不住要上啥课,但是总是去相册找又太麻烦了。恰巧这学期开了Android的课程,于是结合所学以及在网上搜集的资料,就写了一个课表Android小程序。
一、截图展示
程序可以判断当前第几周,自动去除周次不在范围,以及单双周不匹配的课程。
![image-20200406122425961](https://img2020.cnblogs.com/blog/1491349/202004/1491349-20200406135728535-350831219.png)
![image-20200406122410892](https://img2020.cnblogs.com/blog/1491349/202004/1491349-20200406135728403-1200842928.png)
二、程序思路
1、首先确定数据结构
在这里最重要的就是上课时间的这个属性,我们按照特定规则的字符串,以此来存放上课时间,这样再按照特定的算法解析它。这样尽管一周有多节课程名相同,但是单双周或教室不一样的课程也只需要用一个对象来封装他。
如下,计算机信息安全课程,一周有两次课,我们用;
分割不同上课时间的课程,然后再用:
分割具体的上课时间与地点
2、布局
然后将课表分为3个水平Linear layout,周次、星期、上课时间。然后上课时间分为8个垂直Linearlayout。
三、具体实现
1、周次信息
👉我们首先实现最最简单的部分
先在类中声明一个RelativeLayout
,设置好内边距和背景色。
因为周次的信息会变化,所以任然在类中申明一个TextView
,方便修改其中的文字,然后在方法中设置好相关布局参数。
最后在类中申明一个ImageView
,以便给他添加监听事件,同样在方法中设置好布局参数。
private void addWeekTitle(ViewGroup pViewGroup) {
mTitleLayout = new RelativeLayout(mContext);
mTitleLayout.setPadding(8, 15, 8, 15);
mTitleLayout.setBackgroundColor(getResources().getColor(R.color.titleColor));
//周次信息
mWeekTitle = new TextView(mContext);
mWeekTitle.setTextSize(titleSize);
mWeekTitle.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mWeekTitle.setGravity(Gravity.CENTER_HORIZONTAL);
mTitleLayout.addView(mWeekTitle);
//左侧菜单栏
mCategory = new ImageView(mContext);
mCategory.setImageResource(R.drawable.category);
mCategory.setLayoutParams(new LayoutParams(dip2px(30), dip2px(30)));
mTitleLayout.addView(mCategory);
pViewGroup.addView(mTitleLayout);
addHorizontalTableLine(pViewGroup);
}
在这里讲两个后面会常用的方法
/**
* 添加水平线
*
* @param pViewGroup 父组件
*/
private void addHorizontalTableLine(ViewGroup pViewGroup) {
View view = new View(mContext);
view.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, tableLineWidth));
view.setBackgroundColor(getResources().getColor(R.color.viewLine));
pViewGroup.addView(view);
}
👉添加水平线方法布局参数,宽度属性值为LayoutParams.MATCH_PARENT
,会自动匹配父容器宽度,tableLineWidth
呢就是水平线的厚度,在这个程序里我只设置成了1,所以注意看上面周次信息的截图,能看到很细的一条线。
/**
* 添加垂直线
*
* @param pViewGroup 父组件
*/
private void addVerticalTableLine(ViewGroup pViewGroup) {
View view = new View(mContext);
view.setLayoutParams(new ViewGroup.LayoutParams(tableLineWidth, ViewGroup.LayoutParams.MATCH_PARENT));
view.setBackgroundColor(getResources().getColor(R.color.viewLine));
pViewGroup.addView(view);
}
👉同理,添加垂直线的布局参数,高度的属性值为LayoutParams.MATCH_PARENT
,会自动配置父容器的高度,线的厚度如上所述。也就这两个函数构成了我们这个课程表的网格线
2、星期信息
有了上面两个函数的铺垫,这块就好写多了,代码看着有点多,所以先来解释下
👉很容易看出,在这一部分使用线性布局最佳。因此我们先创建一个LinearLayout
,然后给他设置布局参数,方向为水平的,宽度设置为LayoutParams.MATCH_PARENT
,让它与父组件一样大,高度呢设置为titleHeight
,是我们定义的成员变量,自己可以根据需要填合适的值。
在这一行的最左边有个空白符,因为他没有内容,简单设置一下就行。然后星期几这块就可以使用个for循环来生成了,先addVerticalTableLine
添加个垂直线,然后添加个TextView
用来显示星期。
private void addWeekLabel(ViewGroup pViewGroup) {
LinearLayout mTitleLayout = new LinearLayout(mContext);
mTitleLayout.setOrientation(HORIZONTAL);
mTitleLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, titleHeight));
addView(mTitleLayout);
//空白符
TextView space = new TextView(mContext);
space.setLayoutParams(new ViewGroup.LayoutParams(numberWidth, ViewGroup.LayoutParams.MATCH_PARENT));
space.setBackgroundColor(getResources().getColor(R.color.titleColor));
mTitleLayout.addView(space);
//星期
for (int i = 0; i < weeksNum; i++) {
addVerticalTableLine(mTitleLayout);
TextView title = createTextView(weekTitle[i], titleSize, 0, ViewGroup.LayoutParams.MATCH_PARENT, 1, getResources().getColor(R.color.textColor), getResources().getColor(R.color.titleColor));
mTitleLayout.addView(title);
}
}
3、主体部分
下面到了最重要的一个部分,也就是实现中间看起来花花绿绿的地方
/**
* 刷新课程视图
* @param courseMap 课程数据
* @param weekNum 周次
*/
private void flushView(Map<Integer, List<Course>> courseMap, long weekNum) {
//①初始化主布局
if (null != mMainLayout) removeView(mMainLayout);
mMainLayout = new LinearLayout(mContext);
mMainLayout.setOrientation(HORIZONTAL);
mMainLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
addView(mMainLayout);
//②周次标题
mWeekTitle.setText("第 " + weekNum + " 周");
//③左侧节次标签
addLeftNumber(mMainLayout);
//课程信息
if (null == courseMap) {//数据为空
//④数据为空事,中间显示"暂无数据"
addVerticalTableLine(mMainLayout);
TextView emptyLayoutTextView = createTextView("暂无数据!", titleSize, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, getResources().getColor(R.color.textColor), Color.WHITE);
mMainLayout.addView(emptyLayoutTextView);
} else {//不为空
for (int i = 1; i <= weeksNum; i++) {
addVerticalTableLine(mMainLayout);
//⑤添加单天课程
addDayCourse(mMainLayout, courseMap, i);//添加单天要上的课程
}
}
invalidate();
}
①首先创建个水平的线性布局
②用来设置周次信息
③节次标签
节次标签
回看前面的图,能发现左边有个1~9的节次标签。实现方法和前面一样
/**
* 添加左侧节次数字
*/
private void addLeftNumber(ViewGroup pViewGroup) {
//创建一个线性布局
LinearLayout leftLayout = new LinearLayout(mContext);
//垂直
leftLayout.setOrientation(VERTICAL);
//设置线性布局的布局参数,numberWidth就是宽度,高度是LayoutParams.WRAP_CONTENT,用来匹配父容器高度
leftLayout.setLayoutParams(new LayoutParams(numberWidth, ViewGroup.LayoutParams.WRAP_CONTENT));
//循环生成数字
for (int i = 1; i <= maxSection; i++) {
//添加水平线
addHorizontalTableLine(leftLayout);
//数字
TextView number = createTextView(String.valueOf(i), numberSize, ViewGroup.LayoutParams.MATCH_PARENT, cellHeight, 1, getResources().getColor(R.color.textColor), Color.WHITE);
leftLayout.addView(number);
}
pViewGroup.addView(leftLayout);
}
④看着代码一大坨,其实作用就一个,就是在中间显示没有课程,如下图效果(后期改了显示的文字)
![image-20200605183059518](https://img2020.cnblogs.com/blog/1491349/202006/1491349-20200605185823634-1470503236.png)
添加单天课程
⑤在这个flushView
函数中,最重要的也就是addDayCourse(mMainLayout, courseMap, i)
这一行代码了,它用来显示单天的课程信息,所以在外面用个for循环,循环个weeksNum
次(一个星期的天数,也就是7)
首先讲一下方法各个参数的含义。pViewGroup也就是父组件。courseMap是一个Map<Integer, List<Course>>型的map,他的键是第几天,也就是1~7,值是这一天的所有课程。day表示的是当前是一个星期的第几天
/**
* 添加单天课程
*
* @param pViewGroup pViewGroup 父组件
* @param day 星期
*/
private void addDayCourse(ViewGroup pViewGroup, Map<Integer, List<Course>> courseMap, int day) {
//很容易看出,这里应该使用垂直线性布局
LinearLayout linearLayout = new LinearLayout(mContext);
linearLayout.setLayoutParams(new LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1));
linearLayout.setOrientation(VERTICAL);
//第day天的课程信息
List<Course> courses = getCourses(courseMap, day);
if (null != courses) {//如果第day天有课
//通过for循环,显示课程信息
for (int i = 0, size = courses.size(); i < size; i++) {
Course course = courses.get(i);
int section = course.getSection();
if (i == 0) addBlankCell(linearLayout, section - 1);
else
addBlankCell(linearLayout, course.getSection() - courses.get(i - 1).getSection() - 2);
addCourseCell(linearLayout, course);
if (i == size - 1) addBlankCell(linearLayout, maxSection - section - 1);
}
} else {//如果第day天没有有课,添加maxSection个空白块,在此程序中maxSection=9
addBlankCell(linearLayout, maxSection);
}
pViewGroup.addView(linearLayout);
}
解释上面代码中两个函数,一个是addBlankCell
,其作用就是添加一个空白块,用来给没有课程的地方占位,参数num表示添加几个空白块
/**
* 添加空白块
*
* @param pViewGroup 父组件
* @param num 空白块数量
*/
private void addBlankCell(ViewGroup pViewGroup, int num) {
for (int i = 0; i < num; i++) {
addHorizontalTableLine(pViewGroup);
TextView blank = new TextView(mContext);
//设置空白块的大小
blank.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, cellHeight));
pViewGroup.addView(blank);
}
}
另一个函数就是addCourseCell
,其作用就是添加一个课程。(注释写的很详细了,不解释了)
/**
* 添加课程单元格
* @param pViewGroup 父组件
* @param course 课程信息
*/
private void addCourseCell(ViewGroup pViewGroup, Course course) {
//添加水平线
addHorizontalTableLine(pViewGroup);
//RoundTextView是自定义的一个类,其第2个和第3个参数用来表示textview的圆角大小和颜色
RoundTextView textView = new RoundTextView(mContext, radius, getColor(colorMap, course.getCourseName()));
//设置课程块的大小
textView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, cellHeight * 2 + tableLineWidth));
//设置字体的大小
textView.setTextSize(courseSize);
//字体颜色
textView.setTextColor(Color.WHITE);
//字体居中显示
textView.setGravity(Gravity.CENTER);
//内容
textView.setText(String.format("%s\n%s\n%d~%d周\n%s", course.getCourseName(), course.getTeacherName(), course.getStartWeek(), course.getEndWeek(), course.getClassroom()));
//添加到父组件
pViewGroup.addView(textView);
}
最后看一下如何调用这些方法的吧
TimeTableView.java
//构造函数
public TimeTableView(Context context) {
super(context);
this.mContext = context;
initView();
}
//构造函数
public TimeTableView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initView();
}
/**
* 初始化视图
*/
private void initView(){
preprocessorParam();
//周次标题
addWeekTitle(this);
//星期标签
addWeekLabel(this);
//课程信息
flushView(null, weekNum);
}
/**
* 加载数据
*
* @param courses
*/
public void loadData(List<Course> courses, Date date) {
this.courseList = courses;
this.startDate = date;
weekNum = calcWeek(startDate);
//①
handleData(courseMap, courses, weekNum);
flushView(courseMap, weekNum);
}
①这里面handleData方法就是把外部传递过来课程信息courses,如下面的玩意
List<Course> courseList = new ArrayList<>();
courseList.add((new Course("形式与政策", "肖x", 1, 7, "3:5:s:XC-D")));
courseList.add((new Course("计算机信息安全", "李x", 1, 11, "3:5:d:XC207;5:3:n:XC205")));
courseList.add((new Course("软件工程", "彭x", 1, 12, "2:3:n:XC412;5:1:n:XC308")));
courseList.add((new Course("Web前端开发", "罗x", 1, 12, "1:5:n:XC412;3:1:n:XC412")));
courseList.add((new Course("JavaWeb高级编程", "***x", 5, 16, "1:3:n:XC412;3:7:n:XC412")));
courseList.add((new Course("Android应用开发", "卢x", 5, 16, "4:3:n:XD110;5:5:n:XC310")));
courseList.add((new Course("计算机视觉应用", "王x", 1, 8, "2:5:n:XC305;4:1:n:XC409")));
courseList.add((new Course("计算机新技术", "李x", 6, 9, "1:1:n:XC4;1:7:n:XC4;2:1:n:XC4;3:3:n:XC4")));
进行处理,然后处理好的信息放进courseMap中,也就是前面说过的 Map<Integer, List<Course>> courseMap
,其键表示一星期的第几天,值为这一天的所有课程信息,处理过程就不描述了。
MainActivity.java
public class MainActivity extends AppCompatActivity {
private TimeTableView timeTable;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sp = getSharedPreferences("config", MODE_PRIVATE);
timeTable = findViewById(R.id.timeTable);
//获取开学时间
long date = sp.getLong("date", new Date().getTime());
timeTable.loadData(acquireData(), new Date(date));
}
acquireData
方法就是用来获取课程信息List的
————————
虽然这也是我写博客最认真最详细的一次了,但是可能因为没有全部的代码做参照,或者某些地方表达的不好,可能各位道友还是有点不懂,那么只能上大招了,源代码如下
课程表控件链接:点这里
项目地址:https://github.com/HeMOua/timetable
有疑问或其他问题的道友可以留言