Android課程表的實現
以往上課之前都要去相冊找到本學期的課表截圖,不然容易記不住要上啥課,但是總是去相冊找又太麻煩了。恰巧這學期開了Android的課程,於是結合所學以及在網上搜集的資料,就寫了一個課表Android小程序。
一、截圖展示
程序可以判斷當前第幾周,自動去除周次不在范圍,以及單雙周不匹配的課程。


二、程序思路
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);
}
④看着代碼一大坨,其實作用就一個,就是在中間顯示沒有課程,如下圖效果(后期改了顯示的文字)

添加單天課程
⑤在這個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
有疑問或其他問題的道友可以留言