Android课程表App开发实战:数据库设计与界面实现全解析 简介在Android应用开发中数据持久化与界面构建是两项最基础也最核心的能力。SQLite作为轻量级嵌入式数据库为移动端提供了可靠的数据存储方案通过SQLiteOpenHelper可以高效管理表结构与数据操作而RecyclerView凭借灵活的布局管理和视图复用机制成为展示列表或网格数据的首选组件。理解两者的工作原理对于实现一个功能完整的Android应用至关重要。以课程表App为例它需要将课程信息持久化存储并按周次、节次动态展示在网格界面上恰好覆盖了数据库设计、日期计算、状态管理、界面适配等典型开发场景。本文结合AndroidStudio中的完整开发流程详细拆解了课程表App的功能规划、数据库表结构设计、数据访问层封装、网格界面实现及常见踩坑点帮助开发者快速掌握从数据层到表现层的完整实现思路。 最近有不少学弟学妹找我问Android课程设计怎么做我的建议一直是课程表。这个选题听起来普通但做起来五脏俱全——数据库、界面适配、日期计算、状态管理全都要碰一遍工作量不大不小交给老师也能讲出不少东西。加上网上的参考代码很多哪怕你是从零开始只要搞懂核心逻辑三天内交出能运行的课设完全来得及。这篇文章我就用之前在AndroidStudio里做“课程表”课设的完整过程来拆解包含功能规划、数据库设计、界面实现、常见坑点以及提交前要注意的细节。适合正在准备Android期末作业、课程设计或者毕业设计的同学参考也适合想练手Android开发基础的人照着走一遍。1. 项目整体设计与思路拆解1.1 先搞清楚课设要解决什么问题课程表App的核心需求其实非常明确帮学生管理和查看每周的上课安排。但很多同学拿到题目就直接开写结果做出来的东西不是功能太少被老师挑刺就是结构太乱自己都改不动。我的建议是先花半小时做功能拆解。一个能让老师点头的课程表课设至少要有这几块基础功能课程的增删改查、按周展示、选中某一天查看课程明细辅助功能学期周数管理、单双周提示、课程时间段的默认配置数据持久化App关闭重开后数据还在这里用SQLite最稳把这些列清楚之后你再去写代码心里就有底了。我见过不少同学一上来就直奔界面写着写着发现数据存不住又要回头改结构来回折腾浪费时间。课设这东西最重要的是在有限时间内交付一个稳定可演示的项目而不是炫技。1.2 技术选型不追求新追求稳做课设不是搞生产级项目不需要上Jetpack Compose、Room、MVVM那一套。用传统方案反而更好解释也好排查问题。我当时选的组合是语言Java。现在很多学校教材还在教Java用Java写答辩的时候解释起来省心数据库SQLite配合SQLiteOpenHelper手动建表。不用GreenDAO或者Room因为课设重点是展示你对数据库的理解界面XML布局 RecyclerView。课程表主界面用RecyclerView做网格或者用ScrollView嵌套GridView都可以但RecyclerView在答辩时更有说头架构Activity 简单的数据访问类不引入MVP/MVVM。课设强调你能跑通架构太复杂反而容易翻车这套组合的好处是每一步你都知道自己在干什么出了问题也知道去哪个文件里找不像用了框架之后出了问题连日志都看不懂。1.3 项目目录结构怎么规划建议按功能把类分开别把所有代码堆在一个Activity里。我当时的目录结构大概是这样的app/src/main/java/com/example/course_table/ ├── MainActivity.java // 主界面课程表展示 ├── CourseDetailActivity.java // 课程详情/添加编辑页 ├── db/ │ ├── CourseDbHelper.java // SQLiteOpenHelper实现 │ ├── CourseDao.java // 数据访问层 │ └── Course.java // 课程实体类 ├── adapter/ │ └── CourseGridAdapter.java // 课程网格适配器 └── utils/ └── DateUtils.java // 日期、周次计算工具这样的结构面试官或老师一眼就能看清而且你自己维护起来也容易。哪怕你打算在网上找一个开源项目改一改也要先按照这个思路把代码重新整理一遍别直接把原项目的包名和结构扔上去到时候问到你某个类在哪你都说不出来。2. 数据库与数据层设计2.1 课程表数据库到底要建几张表很多同学一开始会想着给每一门课建一张表这是典型的思路错误。课程表App的数据模型不是“一门课一张表”而是用“课程”和“时间安排”分开建模。我当时只建了一张表原因是课设的场景足够简单每个课程在每周固定时间和地点上课。表结构设计如下CREATE TABLE course ( id INTEGER PRIMARY KEY AUTOINCREMENT, course_name TEXT NOT NULL, teacher TEXT, classroom TEXT, day_of_week INTEGER NOT NULL, -- 周一1周日7 start_section INTEGER NOT NULL, -- 开始节次 end_section INTEGER NOT NULL, -- 结束节次 start_week INTEGER NOT NULL, -- 开始周 end_week INTEGER NOT NULL, -- 结束周 week_type INTEGER DEFAULT 0, -- 0每周1单周2双周 color INTEGER DEFAULT 0xFF4CAF50 );字段含义并不复杂但有几个地方需要特别解释一下。day_of_week和start_section、end_section是核心字段用来确定课程在每周的什么时间出现。start_week和end_week让课程可以只在特定周数内显示比如某些课程从第3周才开课。week_type用来支持单双周的区别0表示每周都有1表示只在单周有2表示只在双周有。你可能会问为什么要单独加color字段因为课程表界面上不同课程需要用不同颜色区分存一个ARGB颜色值显示的时候直接读到int值给背景色用比每次去解析字符串方便得多。有的同学想把课程表做得更复杂比如支持“第1-8周9-15周”这种多段区间或者支持调课记录。这些在课设阶段不建议做数据模型复杂度上去了出错概率也直线上升。能覆盖90%场景就是课设优秀水平。2.2 SQLiteOpenHelper的封装写法建表其实不难但要注意几个细节。数据库版本号初始设为1如果后面要加字段版本号1在onUpgrade里处理ALTER TABLE。public class CourseDbHelper extends SQLiteOpenHelper { private static final String DB_NAME course_table.db; private static final int DB_VERSION 1; public CourseDbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE IF NOT EXISTS course ( id INTEGER PRIMARY KEY AUTOINCREMENT, course_name TEXT NOT NULL, teacher TEXT, classroom TEXT, day_of_week INTEGER NOT NULL, start_section INTEGER NOT NULL, end_section INTEGER NOT NULL, start_week INTEGER NOT NULL, end_week INTEGER NOT NULL, week_type INTEGER DEFAULT 0, color INTEGER DEFAULT 0xFF4CAF50)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS course); onCreate(db); } }onUpgrade里直接DROP是课设的常规做法数据一定会丢但简单粗暴。如果你想在老师面前表现得好一点可以改成查列是否存在再进行ALTER TABLE这个属于加分项不做也不影响。2.3 数据访问层让Activity少写SQL虽然课设规模不大但我还是建议把数据操作集中到一个CourseDao类里不要每个页面都写SQLiteDatabase的增删改查。这样后面改逻辑的时候只需要改一个文件。CourseDao的典型方法包括ListCourse getAllCourses()获取所有课程用于导出或全部备份ListCourse getCoursesByDay(int dayOfWeek)获取某一天的课程ListCourse getCoursesForWeek(int weekNumber)获取某周的课程主要判断单双周和起止周long insertCourse(Course course)插入课程并返回idint updateCourse(Course course)更新课程void deleteCourse(int id)删除课程有一点要注意getCoursesForWeek的逻辑要包含三部分判断课程的startWeek和endWeek要覆盖当前周如果weekType是1当前周必须是奇数如果weekType是2当前周必须是偶数。这个逻辑写错了课程表的周次显示就全乱了。我在做课设时因为这个单双周判断第一次写反了导致双周课在单周也显示界面上一周同时出现七八节课排查了好一阵才找到问题。后来我把这个逻辑单独抽成了工具方法前后端都可以复用。2.4 课程实体类的小设计Course类本质上就是数据库表结构的对象映射字段和表一一对应。这里要提一个容易忽略的点颜色值在Java里用int类型数据库里存的也是INTEGER但如果你从数据库读取时用cursor.getInt查出来的是int直接setBackgroundColor用没问题。不要想着把颜色存成文字类型的“绿色”“红色”这样后患无穷。3. 核心界面与功能实现3.1 主界面选择网格还是时间轴课程表App的主界面通常有两种画法一种是传统的校园课程表网格横向是周一到周日纵向是节次另一种是当天课程列表按时间从上往下排。网格视图的好处是信息密度高一眼能看到整周安排坏处是屏幕宽度有限7列课表在手机竖屏下每一列都非常窄课程名字长了容易显示不下。列表视图的好处是显示清楚坏处是缺少“课程表”的直观感觉。我最终做了网格视图理由说出来有点实在课设演示的时候评委老师第一眼看到的是界面像不像课程表网格视图一眼就能认出来加分明显。网格的实现方式有两种。一个是RecyclerView配合GridLayoutManager把一周的课程格子和空闲格子统一当作item处理另一种是ScrollView里套多个横向LinearLayout每一行代表一个节次。前者性能好、代码优雅后者实现简单、适合新手Debug。我当时选了RecyclerView GridLayoutManager列数设为7每个item的高度设为80dp左右。为了显示时定位到某个格子需要根据“周几节次”计算item在列表中的位置公式是position (startSection - 1) * 7 (dayOfWeek - 1);这里从0开始第1节就是0行周一就是第0列。这个公式是整个网格界面能够正确显示的关键代码写得不复杂但很容易因为差一个数字导致整个界面错位。3.2 课程格子的绘制与数据绑定既然用了RecyclerView那么Adapter就是核心。我的CourseGridAdapter布局大概长这样public class CourseGridAdapter extends RecyclerView.AdapterCourseGridAdapter.ViewHolder { private static final int TOTAL_SECTIONS 12; private static final int TOTAL_DAYS 7; private ListCourse courseList; private int currentWeek; private OnCourseClickListener listener; Override public int getItemCount() { return TOTAL_SECTIONS * TOTAL_DAYS; } Override public void onBindViewHolder(ViewHolder holder, int position) { int row position / TOTAL_DAYS; int col position % TOTAL_DAYS; Course course findCourseAt(row 1, col 1); if (course ! null) { holder.tvCourseName.setText(course.getCourseName()); holder.tvCourseClassroom.setText(course.getClassroom()); holder.itemView.setBackgroundColor(course.getColor()); holder.itemView.setOnClickListener(v - listener.onClick(course)); } else { holder.tvCourseName.setText(); holder.tvCourseClassroom.setText(); holder.itemView.setBackgroundColor(Color.WHITE); holder.itemView.setOnClickListener(v - listener.onClickEmpty(row 1, col 1)); } } }findCourseAt这个方法需要遍历当前周的课程列表分别比较dayOfWeek和课时区间是否包含row1开始的那一节课并且只用startSection不用endSection去匹配。也就是说如果一个课程从第1节上到第4节它显示在第1节所在的行里但视觉效果上可以通过设置item高度为4倍来模拟跨行。跨行显示是网格课程表最麻烦的地方之一。最简单的方案是不跨行只在每个格子显示课程名称重复显示多次。我建议课设先用重复方案把时间省下来做其他功能。如果你想做得好看一点可以给那个格子的height设置成endSection - startSection 1倍的格子高度但RecyclerView的item尺寸是固定的需要用SparseArray去动态设置代码量一下子就上来了。我最后为了稳定选择了一个折中方案单节课时跨多个节次的会在第一个格子显示课程名和教室后续格子显示空白但颜色一致。这样视觉上像是连在一起的块实际实现只是在连续格子上设置了相同的背景色。3.3 添加和编辑课程表单页的细节添加课程的页面用一个Activity就够了布局用ScrollView包LinearLayout。表单字段包括课程名称、教师、教室、星期几、开始节次、结束节次、开始周、结束周、单双周类型。这里最容易被忽略的是数据校验。我当时没做校验直接在表单里输入课程名结果用户什么都没填就点了保存数据库里插了一条空数据界面上的格子完全错乱。建议保存前做这几项检查课程名称不能为空结束节次必须大于等于开始节次结束周必须大于等于开始周dayOfWeek范围在1到7之间校验失败时用Toast提示具体原因不要只说“输入有误”用户根本不知道自己错在哪。表单页的另一个细节是Spinner或下拉框选择星期几和节次而不是让用户手动输入数字。手动输入不仅麻烦还可能输错用Spinner框死选项是最稳的做法。3.4 当前周次的计算与联动一个课程表App如果只展示固定课表那和一张图片没区别。为了体现课设的“活”建议把周次计算做进去。思路是这样的开学第一天对应第一周的周一根据今天日期和开学日期计算今天是第几周然后再根据今天是几号算出星期几。当时我用了一个不算新的但很稳的公式public static int getCurrentWeek(Calendar startDate, Calendar today) { long diffMillis today.getTimeInMillis() - startDate.getTimeInMillis(); int diffDays (int) (diffMillis / (1000 * 60 * 60 * 24)); return diffDays / 7 1; }注意这里的天数除法要确保今天如果是周一正好落到对应的周。如果跨了寒暑假这种方法是算不准的但课设不需要处理那种情况。你可以在设置里加一个“开学日期”选项用户手动选日期然后App自动算周次。我建议做一个手动切换周次的按钮在顶部显示“第X周”同时提供上一周、下一周的箭头这样答辩时你可以手动演示任意一周的课表比死板地依赖当前日期要灵活得多。4. 实操过程与踩坑记录4.1 从空项目到跑通整个流程的步骤如果你是在AndroidStudio里从零开始我给一个可抄的步骤清单照着做不会乱新建Empty Activity项目包名建议用com.yourname.coursetable不要用默认的com.example答辩时显得更规范先创建Course类和CourseDbHelper把数据库建好再写CourseDao把增删改查方法补齐临时用AndroidStudio自带的Database Inspector或写一个简单的点击按钮验证数据能存进去写主界面的布局XML把RecyclerView放进去实现CourseGridAdapter让课程数据显示出来写CourseDetailActivity表单页打通添加流程加入周次切换、单双周判断美化界面调整颜色和间距反复测试重点测试跨周课程和单双周课程这套顺序有一个原则先把数据层跑通再做界面。很多同学喜欢先调界面结果界面上点了没反应不知道是数据的问题还是界面绑定的问题排查效率极低。4.2 AndroidStudio常见环境问题虽然是课设但环境问题往往比代码问题更让人崩溃。这里列几个我当时和身边同学踩过的坑。第一个是模拟器启动不了。AndroidStudio自带的AVD经常在电脑配置一般的情况下卡在加载界面一个简单的办法是改用MuMu模拟器或Genymotion用adb连接adb connect 127.0.0.1:7555如果adb连不上把模拟器里的开发者选项打开开启USB调试。很多同学安装了模拟器却忘了开调试模式导致IDE一直识别不到设备。第二个问题是SDK路径或依赖下载失败。AndroidStudio安装时如果使用了国内网络环境Gradle依赖经常下载不完整。这时可以在项目的build.gradle里配置国内镜像源同时关闭Gradle的离线模式让依赖重新下载。第三个问题是AndroidManifest.xml忘了配置Activity。新增的Activity必须在AndroidManifest里注册否则运行时点击按钮直接崩报ClassNotFoundException。如果你在写代码时发现New Activity创建完但没自动注册别靠猜打开AndroidManifest看一眼。4.3 课程表显示错位的排查思路我在调试网格课程表时最常遇到的问题有这三个这里直接给出排查路径第一点击某一节课位置发现进入详情页但课程不对。问题多半出在position到(row, col)的换算公式上建议在onBindViewHolder里加日志打印positionrowcol一目了然。第二课程重叠显示某一个格子显示了好几节课。这通常是因为findCourseAt里面只用dayOfWeek和startSection没有判断当前格子是否在该课程的连续区间内。你要在循环里同时判断startSection currentSection currentSection endSection并且把整个格子全部遍历一遍。第三单双周课程显示不对。这个就是之前提到的周次判断逻辑错了。写一个Utils方法专门判断然后单测覆盖几种边界情况。4.4 界面细节的加分项要说课设拿高分的秘诀功能完整是基础界面细节是加分。我总结几个性价比很高的美化点每个课程格子用不同颜色让课表看起来五彩斑斓。颜色可以预置10种在添加课程时随机给一个当前时间所属的节次格子用浅灰色背景标出来顶部显示当前的“第X周”旁边放上一周和下一周切换按钮每个格子同时显示课程名和教室字体一大一小视觉层次清楚点击空白格子可以直接跳转到添加页面并且自动带上你点击的星期和节次最后这条特别实用用户体验提升明显而且代码量只要几行。5. 提交前一定要做的事5.1 真机测试一遍模拟器上运行没问题不一定代表真机没问题。很多同学在模拟器上看着挺美一放到真机上就出现状态栏高度不对、字体显示太小的适配问题。建议借一部Android手机USB调试连上跑一遍完整流程添加几门不同类型的课程、切几周看看显示、杀掉App重新打开确认数据还在。真机跑通了答辩的时候心里就有底了。5.2 准备一个演示话术课设答辩不只看代码跑不跑得通还要看你能不能讲清楚。我建议准备5分钟演示脚本按这个顺序讲打开App介绍这是课程表App展示主界面的周视图点添加按钮现场添加一门课程说明课程有哪些字段回到主界面展示新课程已经出现在对应的时间格子里切换周次展示单双周课程如何变化重启App说明数据存在SQLite里不会丢失如果你还能简单说一下“这里用了SQLiteOpenHelper管理数据库用RecyclerView来展示网格数据”基本就能拿到不错的分数了。5.3 代码注释和命名规范给课设写代码不用追求花哨但要注重可读性。关键的类加几行注释变量命名用完整单词而不是a、b、c。老师翻代码的时候看到清晰的命名和注释印象分会高很多。一些小经验最后分享一个我在做课程表课设时收获最大的习惯不要急着写界面先把数据模型定义清楚。课程这个实体类虽然简单但一旦字段设计好了后面从头到尾都在用它。字段命名、类型、含义这些只要有一个不合适后期所有代码都要跟着改越早想清楚越省钱。还有一点想提醒你网上能找到很多现成的课程表项目但直接下载交上去风险很大一方面是老师可能查重或让你现场讲解另一方面是别人的代码里可能藏着你根本看不懂的逻辑。最好的做法是你看清楚别人的实现思路然后自己动手敲一遍哪怕界面丑一点但每一行代码你都知道它在干嘛答辩时自然底气十足。课程表这个题目做得好不好关键不在功能多不多而在基本功稳不稳。该建的数据库建好该算的周次算对该做的校验补齐界面干净整洁这就是一个非常优秀的Android课设。祝你能顺利交差答辩现场也能稳稳发挥。本文还有配套的精品资源点击获取