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

有疑问或其他问题的道友可以留言

posted @ 2020-04-06 14:01  贺墨于  阅读(9274)  评论(7编辑  收藏  举报