Android課程表的實現


Android課程表的實現

以往上課之前都要去相冊找到本學期的課表截圖,不然容易記不住要上啥課,但是總是去相冊找又太麻煩了。恰巧這學期開了Android的課程,於是結合所學以及在網上搜集的資料,就寫了一個課表Android小程序。

一、截圖展示

程序可以判斷當前第幾周,自動去除周次不在范圍,以及單雙周不匹配的課程。

image-20200406122425961 image-20200406122410892

二、程序思路

1、首先確定數據結構

image-20200406122845067

在這里最重要的就是上課時間的這個屬性,我們按照特定規則的字符串,以此來存放上課時間,這樣再按照特定的算法解析它。這樣盡管一周有多節課程名相同,但是單雙周或教室不一樣的課程也只需要用一個對象來封裝他。

如下,計算機信息安全課程,一周有兩次課,我們用;分割不同上課時間的課程,然后再用:分割具體的上課時間與地點

image-20200406123415215

2、布局

然后將課表分為3個水平Linear layout,周次、星期、上課時間。然后上課時間分為8個垂直Linearlayout。

image-20200406123638665

三、具體實現

1、周次信息

image-20200519153625657

👉我們首先實現最最簡單的部分

先在類中聲明一個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、星期信息

image-20200519160421068

有了上面兩個函數的鋪墊,這塊就好寫多了,代碼看着有點多,所以先來解釋下

👉很容易看出,在這一部分使用線性布局最佳。因此我們先創建一個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

添加單天課程

⑤在這個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

有疑問或其他問題的道友可以留言


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM