news 2026/10/6 5:53:47

安卓答题App完整工程解析:SQLite题库、倒计时与避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
安卓答题App完整工程解析:SQLite题库、倒计时与避坑指南

简介:基于Android Studio开发的一款安卓答题App完整项目,面向Android初学者、课程设计者及有测验类应用需求的学习者。内置限时答题、选择题作答、即时判断对错并反馈正确答案、答题结果统计、错题集自动收集与历史成绩保存,覆盖从题库加载到成绩管理的主要流程。资源共50个文件,压缩包大小8.81MB,包含Java源码、XML布局、Gradle构建配置、PNG图标、properties属性文件及docx运行说明文档,附有可直接安装的APK和演示视频,还打包了题库数据。已有5888人学习,目录结构清晰,标准Android Studio工程布局便于定位与修改;结合运行文档与视频,既能快速部署体验,也能深入理解答题状态管理、错题持久化和统计逻辑,适合作为毕业设计、课程作业的参考。

1. 安卓答题App:一个能练手也能直接改的完整工程

做课程设计、公司内部考核工具、或者给培训机构搭一个轻量考试系统,最省事的路径不是从零写界面,而是拿一份能跑的安卓答题工程来改。这份基于Android Studio的答题App,就是把「题目展示、选项交互、计时、评分、错题回顾」这些核心环节全打通了的完整项目,不是只给一个登录页面或者静态列表的demo。适合刚学完四大组件、想看看完整App怎么串起来的初学者,也适合手里有现成题库、想快速做成App交付的开发者。你能直接改包名、换图标、替换题库,发布成自己的应用。下面我会按工程结构、数据库设计、答题流程、坑点排查这个顺序,把这套工程从里到外拆一遍,方便你对照源码复现。

2. 工程与技术选型:Android Studio 工程结构、语言与依赖的取舍

2.1 工程目录模块划分:三个页面骨架与一个全局状态类

打开这份工程,首先看的是app/src/main/java下的包结构。按功能划分,代码主要分成三块:主界面(MainActivity)、答题页(QuizActivity)、结果页(ResultActivity),另外还有一个独立的档案类(QuestionBank)和全局的当前答题状态管理类(QuizState)。这种「三页面 + 一数据库 + 一状态类」的组合,是小型答题 App 最常见的骨架,好处是页面跳转逻辑清晰,什么页面负责什么事一眼就能看出来,后期要往里面加登录、排行榜也容易。

Android Studio 打开工程的第一步,先确认 Gradle 和 SDK 版本对不对。工程里build.gradle文件规定了编译依赖,常见配置类似这样:

android { namespace 'com.example.quizapp' compileSdk 34 defaultConfig { applicationId "com.example.quizapp" minSdk 21 targetSdk 34 versionCode 1 versionName "1.0" } } dependencies { implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'com.google.android.material:material:1.11.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' }

这里面 minSdk 21 意味着 Android 5.0 以上都能跑,这个覆盖范围在实际使用中基本够用。compileSdk 34 是当前主流编译版本,如果你的 Android Studio 提示 SDK 缺失,去 SDK Manager 里把对应版本装上就行。如果下载后打不开,十有八九是 Gradle 版本和 Studio 版本不匹配,这时候看 Android Studio 的版本要求,AGP 8.x 对应 Gradle 8.x 以上,别硬试旧版本,直接升级 Studio 最省时间。

2.2 语言选型:Kotlin 还是 Java,为什么推荐跟着工程默认走

现在 Android Studio 新建项目默认语言是 Kotlin,这份答题工程如果注释里出现lateinit var或by viewModels()这类写法,就是 Kotlin 写的;如果大量出现findViewById(R.id.xxx)和.setOnClickListener,就是 Java 写的。不管哪种,第一步都是先把工程跑起来,不要上来就重写语言。

我一般建议新手跟着工程原有语言走,因为代码和布局文件是配套的,你自己混着改很容易踩坑。Kotlin 在写异步和判空时更省事,比如后台加载题库用协程,代码会短一截。如果你用的是 Java 版本,也别急着迁移,核心逻辑是一样的,读起来差异不大。这个工程的 UI 交互逻辑集中在答题页,你改题目跳转或者加倒计时,处理的核心是同一个方法链路,语言只是语法差别。

2.3 题库存储选型:三种方案对比

题库存哪,是答题 App 绕不开的决定。这份工程到底用哪种,需要你打开代码看一下QuestionBank类的实现:

存储方式适用场景优点缺点
SharedPreferences 存 JSON题目量 < 100,临时演示实现最简单,几行代码就能读题目一多加载慢,不便查询
SQLite 原生题目几百到几千,结构固定轻量、无额外依赖、查询灵活手写 SQL 容易犯错
Room题库大、要频繁改查编译期校验 SQL,和 LiveData 配合好需引入 kapt 或 ksp,依赖多一点

如果你拿到的工程里用了一整条建表 SQL 和SQLiteOpenHelper,那就是原生 SQLite 方案,这种写法最直观,也最容易改造成 Room。我的习惯是:题目在 500 道以内、以单选题为主、不需要在线更新题库时,SQLite 原生写法完全够用;等你要做后台推送题库、题目带图片解析、按分类精确查询时,再考虑换 Room。别听别人说 Room 是趋势就硬换,小项目用原生 SQLite 维护成本更低,踩坑也少。

3. 题库与数据层:SQLite 建表、初始化导入与随机抽题的实现细节

3.1 建表设计与字段语义:题意、选项、答案、分类怎么装

打开QuestionBank.java或QuizDatabaseHelper.java,核心是一段建表语句。典型的答题表结构如下:

CREATE TABLE QUESTIONS ( _id INTEGER PRIMARY KEY AUTOINCREMENT, question_text TEXT NOT NULL, option_a TEXT NOT NULL, option_b TEXT NOT NULL, option_c TEXT, option_d TEXT, correct_answer TEXT NOT NULL, category TEXT DEFAULT '默认分类', difficulty INTEGER DEFAULT 1 );

在这里option_c和option_d允许为空,是为了兼容只有两个选项的判断题;correct_answer存的是 "A""B""C""D" 这种字母,而不是具体选项文本,这样后续判断对错只需要比对字母,不用做字符串匹配。difficulty这个字段很多初学者不理解为什么要留,其实它是给「按难度抽题」和「错题重练优先出简单题」预留的扩展点。假如你后面要做分级考试,这个字段就能直接派上用场,不用回头改表结构。

很多工程还会单独建一张配置表EXAM_CONFIG,存考试时长、每题分值、是否随机打乱选项这些参数。这样做的好处是改考试规则不用重新编译 App,改数据库就行。如果你拿到的版本没有这张表,建议自己加,后面第 5 章我们会提到与它相关的一个坑。

3.2 首次启动初始化题库:assets 目录导入与自带题目的自动落库

题库初始化是新手最容易卡住的地方——明明代码里有题目数据,但安装到手机后就是查不到,原因是「初始化只写了代码,没写触发逻辑」。常见的触发方式有两种:一种是onCreate里每次打开数据库都检查表是否为空,为空则从assets目录读入;另一种是用SharedPreferences记录一个「已初始化」标志,只有第一次启动时才执行建表和数据导入。第二种更安全,避免每次启动都查一次表的开销。

@Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_SQL); boolean firstRun = prefs.getBoolean("db_inited", false); if (!firstRun) { insertBuiltInQuestions(db); prefs.edit().putBoolean("db_inited", true).apply(); } }

insertBuiltInQuestions函数内部就是反复执行INSERT语句。这里有个细节要注意:insertBuiltInQuestions(db)如果你已经把题库改成从assets目录的 JSON 文件读取,那这里就改成loadJsonFromAssets()再加parseAndInsert。不管怎么改,都要记住在onCreate里先建表、再判断是否导入,顺序反了会直接抛no such table异常,日志里报错指向getWritableDatabase()那一行,这个错误在下一章我们会展开说。

3.3 随机抽题的三种写法:SQL 随机、集合打乱与性能边界

题目加载完,下一个核心需求是乱序出题。最直接的写法是 SQL 里的ORDER BY RANDOM(),但它的性能随着题量增大而下降,500 道题没感觉,5000 道题时下拉加载会有一点延迟。中间路线是查全部_id,在应用层用Collections.shuffle打乱后按顺序取题。工程里如果用到 RecyclerView 逐题展示,通常会先加载题目列表,再打乱一个存放_id的数组,按打乱后的顺序取题目。

SQLiteDatabase db = helper.getReadableDatabase(); Cursor cursor = db.rawQuery( "SELECT _id, question_text, option_a, option_b, option_c, option_d, correct_answer FROM QUESTIONS " + "WHERE category = ? ORDER BY RANDOM() LIMIT ?", new String[]{category, String.valueOf(limit)} );

这段 SQL 的ORDER BY RANDOM()适合单次读题就决定好顺序的场景,配合LIMIT控制题目数量。但要注意,如果工程师想实现「同一套题不同人乱序」,就必须把打乱结果存下来,否则每次进页面顺序都不一样,就会出现两个人明明做同一套题、实际题目顺序完全不同的问题。这里的取舍是:演示用随机 SQL 没关系,正式考试或用于评测就一定要在应用层固定种子再打乱。参数上,category传空字符串查全表,传具体分类则按分类过滤;limit控制出题数,常见设置是单选题 20 道、判断题 10 道。

数据库这块的另一个常见问题是「真机上运行报unable to open database file」,原因是手机存储空间不足或路径不对,logcat 会打印详细路径,照着路径去设备管理器里确认目录是否存在即可。我一般会在导入数据后立刻用db.query(...).getCount()打一条日志,确认完成了再开下一段逻辑,方便问题定位。

4. 答题主流程:UI 交互、倒计时判定、得分累计与错题回看

4.1 答题页 UI 布局与选项点击逻辑:题目展示、四个选项、上一题下一题

答题页布局用 ConstraintLayout 或 LinearLayout 都行,核心要素是:顶部标题栏显示当前题号和剩余时间,中间一块 CardView 显示题干,下面四个按钮或 RadioButton 显示 A/B/C/D 选项。用 RadioButton 的好处是自带互斥点击,但需要注意的是:如果用户直接点击了某个选项跳转下一题,下一题必须调用clearCheck()清空选中状态,否则上一题的答案会带到这一题。

optionsGroup.setOnCheckedChangeListener { group, checkedId -> if (checkedId == -1) return@setOnCheckedChangeListener val answer = when (checkedId) { R.id.radioA -> "A" R.id.radioB -> "B" R.id.radioC -> "C" R.id.radioD -> "D" else -> "" } currentAnswer = answer selectedAnswers[currentIndex] = answer }

这里用了一个selectedAnswers数组来存每道题用户选过的答案,而不是仅仅存当前题目的选项。这样用户点「上一题」返回时,能恢复之前选过的选项,体验上不会出现「回去一看白选了」的翻车情况。代码中的currentIndex是当前题目在列表中的下标,掉头改题时,直接从数组还原 UI 状态。如果这段代码在点击时没有记录答案,那么翻页后选项全空,这在逻辑上是被允许的,因为很多答题 App 支持「做完再回头检查」,所以用数组冗余记录是标准做法。

4.2 倒计时实现与边界处理:全局计时器、退到后台、时间到自动交卷

倒计时在答题 App 里是问题多发区。用CountDownTimer写起来最直接,但它的坑在于手机休眠或切到后台时,Timer 可能被系统挂起,导致用户回来时时间还在,却多出几分钟。更稳的方案是记录考试开始的时间戳System.currentTimeMillis(),每次界面刷新时用当前时间减开始时间计算剩余时间,这样就算 App 被切后台再切回来,剩余时间也是准确的。

long startTime = System.currentTimeMillis(); long durationMillis = 60 * 60 * 1000; // 60分钟 CountDownTimer timer = new CountDownTimer(durationMillis, 1000) { @Override public void onTick(long millisUntilFinished) { long elapsed = System.currentTimeMillis() - startTime; long remain = durationMillis - elapsed; if (remain <= 0) { showTimeUpDialog(); finishQuiz(); } textViewTime.setText(formatTime(remain)); } @Override public void onFinish() { showTimeUpDialog(); finishQuiz(); } };

这个写法的妙处在于onTick里以系统当前时间重新校准剩余时间,即使用户通过某些方式暂停了 Timer,下一次onTick到来时也会把时间纠正回来。formatTime(remain)通常格式化成mm:ss,显示在标题栏右侧。这里的一个细节是当remain <= 0时要走到finishQuiz(),里面调用结果页并传入答题数据,否则会出现倒计时显示 00:00 还在答题的 bug。

4.3 得分计算与错题收集:答对累加、答错进错题本、结果页回显

交卷后的逻辑分为两步:遍历selectedAnswers与数据库的正确答案对比,得满分值;再筛选出错误项,写入错题记录表。得分统计不要太复杂,直接一个 for 循环逐题比对即可。

int score = 0; StringBuilder wrongIds = new StringBuilder(); for (int i = 0; i < selectedAnswers.length; i++) { if (selectedAnswers[i].equals(correctAnswers[i])) { score += eachScore; } else { wrongIds.append(questionIds[i]).append(","); } } saveWrongQuestions(wrongIds.toString());

saveWrongQuestions这个方法把错题 id 拼成 "1,5,23," 这样的字符串存到新表里或SharedPreferences。拼成字符串而不是逐条插入多条记录,是为了减少数据库操作次数,代价是之后查错题时需要split(",")后逐条查询。工程里如果只能进一次考试、没有错题重练入口,这个saveWrongQuestions方法可能被注释或省略。

结果页ResultActivity接收总分、答对数、总题数三个值,通过 Intent 传递,最后用setText显示。这里需要注意的一个坑:ResultActivity 必须标记成android:launchMode="singleTask"或在跳转时清掉之前的 Activity 栈,否则用户按返回键会回到答题页,出现「考完还能改答案再重新交卷」的漏洞。代码写法是加Intent.FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK,这个细节我们下一章专门记录。

5. 避坑与常见问题排查:运行、打包、使用中的五个翻车点

5.1 点击选项没反应,radioGroup 监听不到

现象:界面显示正常,点击四个选项按钮毫无反馈,OnCheckedChangeListener里的 Log 也不打。原因:选项不是 RadioButton 而是 Button,或者 Button 没有设置setOnClickListener,只在 RadioButton 上设了监听,而布局模板里用的是 CheckBox。解决:打开activity_quiz.xml确认控件的类型,如果是 Button,就改用setOnClickListener单独处理每次点击的判断。另一个可能原因是你在onCreate里执行了optionsGroup.clearCheck(),该方法会触发一次checkedId == -1的回调,如果你的代码没有对-1做保护,事件就被吞掉了。解决方法是像第 4.1 节那样,在回调开头先判断if (checkedId == -1) return;。

5.2 横竖屏切换后答题进度清零

现象:答到第 18 题,转一下手机屏幕,页面回到第 1 题且之前选过的答案全丢。原因:默认情况下屏幕旋转会销毁并重建 Activity,答题页的数据没有被onSaveInstanceState保存。解决:在QuizActivity里重写onSaveInstanceState存当前题号和selectedAnswers数组,在onCreate的savedInstanceState分支恢复数据;或者偷懒一点,在 AndroidManifest 里给 Activity 加android:configChanges="orientation|screenSize",这样屏幕旋转时不会重建 Activity,数据还在。两种方案都行,但注意configChanges方式治标不治本,如果系统字体大小变化或强制深色模式切换,它仍然可能触发重建。

5.3 倒计时在模拟器上正常,真机上时间越跑越快

现象:模拟器倒计时 60 分钟正常,装到手机上变成 45 分钟就交卷,或是一小时才过了三十分钟。原因:CountDownTimer的回调间隔不是绝对准确的,受系统省电模式和应用进程调度影响,回调会被延迟。如果在onTick里每次减 1 秒来计算剩余时间,就会出现误差累积。解决:改用我们在 4.2 节写的「记录开始时间戳 + 每次用当前时间反推剩余时间」,误差只在于最后一次校准前的延迟,不会累积。如果你已经用旧写法发布出去了,遇到这种问题不要慌,把计时逻辑替换成时间戳方案重打包一次即可。

5.4 交卷后按返回键能回到答题页,还能再次交卷

现象:结果页点返回,回到答题界面,此时改两个选项再点交卷,分数变了,错题列表也变了。原因:没有清理 Activity 栈,答题页和结果页之间存在返回链路。解决:跳转结果页时用startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK))把整个栈清空,或者在 ResultActivity 的onBackPressed(OnBackPressedDispatcher)里直接finish()并把答题页一并 finish。工程如果在跳转结果页前没有关闭答题页,逻辑上就存在这个漏洞。从那次线上考试有人反复刷分之后,我每次写交卷跳转都会强制把finish()放在触碰之前。

5.5 打包 App 体积小,但删除应用后重新安装题库数据丢失

现象:第一次安装后能答题,卸载重装后打开 App 题库是空的,或者提示「题库表不存在」。原因:SQLiteOpenHelper的实例保存在应用私有目录,卸载应用后数据会被一并清除,而onCreate里的初始化只有在数据库文件不存在时才会执行,第二次安装数据库文件当然不存在,但你的代码没有触发重建。解决:在getReadableDatabase()之前先判断文件是否存在,或者每次启动时都执行一次「检查表是否有记录」的逻辑,为空就再导入一次初始化数据。最常见的原因还是出在db_inited这个SharedPreferences标志没有在卸载时被清除,所以你要把判断改成「数据库文件不存在」而不是「标志不存在」。

6. 进阶玩法:Excel 题库直接转 JSON 导入、增加图片解析、打包前的最后检查

6.1 让非程序员同事也能维护题库:Excel 转 JSON 的标准转换流程

工程交付给别人后,最常听到的需求是「题库怎么换」。如果每次都要改 Java 数组再重新打包,更新一次考试内容就要等一个版本周期。解决思路:把QuizActivity的数据源改成读assets/questions.json,外部维护题库的人只需要维护一个 Excel 文件,然后用脚本转成 JSON 再丢进 assets。Excel 的列结构固定成:题目、A、B、C、D、正确答案、分类、难度,导出成 CSV 后转 JSON。

import csv import json questions = [] with open('quiz.csv', 'r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) for row in reader: questions.append({ "question": row["题目"], "options": [row["A"], row["B"], row["C"], row["D"]], "answer": row["正确答案"], "category": row["分类"], "difficulty": int(row["难度"]) }) with open('questions.json', 'w', encoding='utf-8') as f: json.dump(questions, f, ensure_ascii=False, indent=2)

这个脚本的逻辑很直白:读 CSV,逐行组装字典,最后整体写进 JSON。运行时确保 CSV 的表头和这里保持一致,尤其是题目和A这些列名不能改。题库维护人员更新完 Excel 导出 CSV 再跑一次脚本,就能生成新的题库文件替换 assets 下的旧文件。这么做以后,换题目不需要碰代码,只要重新打包或放在服务器上供 App 动态下载更新。注意 CSV 的编码,Excel 另存为 CSV 时默认可能是 ANSI,脚本里encoding='utf-8-sig'就是兼容带 BOM 的 UTF-8,避免中文乱码导致解析失败。

6.2 给题目绑定解析图片与音频的扩展思路

题库表结构建议预留一个explain_content字段,存文字解析,再留一个explain_image的 URL 或资源路径。答案判定之后,如果答错了,弹出底部对话框显示解析文字和图片,体验会比只告知「正确答案是 B」好很多。这个扩展改造点不复杂:数据库加字段,答题页加一个TextView和ImageView,在showResult分支里填充。

比较现实的问题是你拿到的这份工程可能不支持explain_*字段,这时不要改表结构,而是加一张辅助表QUESTION_EXPLAIN,主键关联_id,额外存图片路径。这样老数据不迁移,新题目的解析也能正常显示。如果你不想动数据库,退一步的做法是在assets下按题目 id 起名放图片,比如explain_001.jpg,Java 代码直接按 id 拼接路径加载,也不复杂。

6.3 打包前检查清单:签名、版本兼容、真机测试与动态权限

无论改了多少东西,发布前我一定按这个清单走一遍,节省大量「装上去闪退」的时间:

  • minSdk 21 的情况下,低版本手机如果用了AndroidX的AppCompat,确认主题继承自Theme.AppCompat。
  • targetSdk如果 30+,注意 Android 11 的包可见性限制:如果需要跳转到系统设置或检测其他应用,要加<queries>声明;如果不需要,则不用管。
  • 动态权限:如果答题 App 要读取本地导入题库文件,需要申请存储权限;读assets不需要权限,但如果用户自己导入外部 JSON 文件,就需要READ_EXTERNAL_STORAGE或分区存储下的MANAGE_EXTERNAL_STORAGE。
  • 签名:用 Android Studio 自带的 Generate Signed Bundle/APK 生成 keystore,千万别把 keystore 放进工程目录一起打包,丢了就找不回来,谁也没办法帮你恢复市场账号的签名。
  • 模拟器跑通的流程,真机大概率要再调一遍。热词里有人问「安卓模拟器哪个最好」,我的习惯是先用 Android Studio 自带模拟器跑逻辑,再用自己手机真机调指纹、调权限弹窗。特别是倒计时、息屏再亮屏这种场景,模拟器测不出来,只能真机复现。

从那次倒计时真机翻车之后,我每次改完代码都会强制在真机上跑一遍「息屏、切后台、亮屏回来」的完整流程,才敢发出去给测试。这个习惯救了我很多次,也建议你用这份工程复现时,把它当成固定步骤。希望这些拆解和避坑记录能帮到你,少走几步我走过的弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/6 5:53:09

SSE流式原理到LangChain结构化输出:打字机效果与JSON解析全方案

从 SSE 流式原理到 LangChain 结构化输出&#xff1a;打字机效果与 JSON 解析全方案实战现在的 AI 应用&#xff0c;谁还没个打字机效果都不好意思上线。但说实话&#xff0c;我看过太多项目把“流式输出”做成了摆设——前端拿到一堆碎文本直接拼上去&#xff0c;后端一个yiel…

作者头像 李华
网站建设 2026/10/6 5:52:47

网络新闻评论情感分类:从特征工程到论据词语的关键实践

简介&#xff1a;基于机器学习的网络新闻评论情感分类研究是一份面向自然语言处理与文本挖掘方向研究者的学术论文PDF&#xff0c;针对网络新闻评论的短文本、口语化等特点&#xff0c;系统解决了如何自动判别评论情感倾向的问题。资源包内共有1个PDF文件&#xff0c;大小仅392…

作者头像 李华
网站建设 2026/10/6 5:52:44

Toonflow:小说驱动的AI短剧生成工作流

简介&#xff1a;Toonflow是一款面向短剧与漫剧创作者的AI自动化生成工具&#xff0c;适用于希望快速将小说转化为完整视听内容的个人开发者、独立创作者及小型内容团队&#xff0c;有效解决剧本编写、视觉素材生成与成片输出流程繁琐、人力成本高的问题。资源包共190个文件&am…

作者头像 李华
网站建设 2026/10/6 5:52:42

LangGraph构建可自我修正的代码生成Agent

1. 这不是“写完就交”的代码生成&#xff0c;而是会自己重写的Agent我第一次把“用LangGraph写个能自测自修的代码生成Agent”这个需求丢给团队新人时&#xff0c;他花三天搭出了一个调用LLM返回Python函数的链路&#xff0c;跑通了Hello World。结果第二天产品提了个真实需求…

作者头像 李华
网站建设 2026/10/6 5:52:40

非游戏开发者用AI做微信小游戏:从开发到备案上线全记录

1. 一个非游戏开发者的真实起点我做了八年后端开发&#xff0c;主要写Java和Python&#xff0c;游戏开发的经验基本为零。Unity没碰过&#xff0c;Cocos只会新建项目&#xff0c;微信小游戏对我来说一直是个“看起来不难但不知道从哪下手”的东西。直到去年年底&#xff0c;我想…

作者头像 李华
网站建设 2026/10/6 5:52:29

AI短剧工具实测:从剧本到成片的完整工作流与踩坑指南

这周在杭州待了几天&#xff0c;正好赶上圈里几个人凑在一起聊AI短剧。有个老哥直接掏出手机给我放了一条刚刚跑完的样片&#xff0c;古风、滤镜、镜头语言都像模像样&#xff0c;结果主角的脸在第三分钟换了一张。他摊手说这是第五稿&#xff0c;崩脸问题始终解决不掉。我问他…

作者头像 李华