【Flutter for OpenHarmony】Flutter三方库PHQ-9抑郁量表的鸿蒙化适配与实战指南
本文介绍了PHQ-9抑郁量表在Flutter中的实现与应用。PHQ-9是国际通用的抑郁症筛查工具,包含9个问题,评估过去两周的情绪状态。作者详细说明了量表的评分标准(0-27分)和结果解读分级(从无抑郁到重度抑郁),并分享了在Flutter中实现该量表的技术方案,包括数据模型设计、题目选项配置和结果解读逻辑。该实现既可作为心理健康自评工具,也展示了Flutter在医疗健康领域的应用潜力,帮助用户了
【Flutter for OpenHarmony】Flutter三方库PHQ-9抑郁量表的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、什么是PHQ-9量表?
我是 IntMainJhy,上海某高校大一计算机专业的学生。说起 PHQ-9 量表,我真的花了不少时间研究。
PHQ-9 的全称是 Patient Health Questionnaire-9,中文名叫"患者健康问卷-9项"。它是临床上最常用的抑郁症筛查工具之一,由美国哥伦比亚大学的 Robert L. Spitzer 等人于1999年开发。
为什么我要做这个功能?
说实话,一开始我是被室友安利的。他说他在国外留学的时候,学校心理咨询中心就推荐他用 PHQ-9 做自我筛查。我一听,感觉这个东西挺有意思的,就想能不能做成一个 Flutter App。
后来我查了更多资料才发现,心理健康筛查在国内其实是刚需。根据统计数据,我国抑郁症患者已经超过9500万,但就诊率只有不到20%。很大一个原因是大家不知道自己的状态算不算"有问题"。
所以我就想,能不能做一个简单的自评工具,让大家能够了解自己的心理健康状态?
二、PHQ-9量表详解
2.1 量表内容
PHQ-9 一共有9道题,用来评估一个人过去两周的情绪状态:
| 题号 | 问题描述 |
|---|---|
| 1 | 做事时提不起劲或没有兴趣 |
| 2 | 感到心情低落、沮丧或绝望 |
| 3 | 入睡困难、睡不安稳或睡眠过多 |
| 4 | 感觉疲倦或没有活力 |
| 5 | 食欲不振或吃太多 |
| 6 | 觉得自己很糟,或觉得自己很失败让家人失望 |
| 7 | 对事物专注有困难,例如看报纸或看电视时 |
| 8 | 动作或说话速度缓慢,或与此相反(坐立不安、来回走动) |
| 9 | 有不如死掉或用某种方式伤害自己的念头 |
2.2 评分标准
每道题都有4个选项,代表过去两周内出现该症状的频率:
| 选项 | 含义 | 分数 |
|---|---|---|
| 完全没有 | 过去两周没有或几乎没有 | 0分 |
| 好几天 | 1-6天 | 1分 |
| 一半以上天数 | 7-11天 | 2分 |
| 几乎每天 | 12-14天 | 3分 |
总分范围:0-27分
2.3 结果解读
| 分数 | 抑郁程度 | 建议 |
|---|---|---|
| 0-4 | 无或极轻微 | 继续保持良好的生活习惯 |
| 5-9 | 轻度抑郁 | 建议尝试放松技巧,保持社交 |
| 10-14 | 中度抑郁 | 建议咨询心理咨询师 |
| 15-19 | 中重度抑郁 | 强烈建议寻求专业帮助 |
| 20-27 | 重度抑郁 | 需要立即寻求专业治疗 |
三、在Flutter中实现PHQ-9
3.1 数据模型
// lib/mental_health/models/quiz_model.dart
/// 测试类型
enum QuizCategory {
phq9('PHQ-9 抑郁量表', '评估过去两周的抑郁症状', Color(0xFF6C63FF), 9, 27),
gad7('GAD-7 焦虑量表', '评估过去一个月的焦虑水平', Color(0xFFFF6B6B), 7, 21);
final String name;
final String description;
final Color color;
final int questionCount;
final int maxScore;
const QuizCategory(this.name, this.description, this.color,
this.questionCount, this.maxScore);
}
/// 测试题目
class QuizQuestion {
/// 题目文本
final String questionText;
/// 选项列表
final List<String> options;
/// 每个选项对应的分数
final List<int> scores;
const QuizQuestion({
required this.questionText,
required this.options,
required this.scores,
});
/// 获取选项数量
int get optionCount => options.length;
/// 获取最高分
int get maxScore => scores.isNotEmpty ? scores.reduce((a, b) => a > b ? a : b) : 0;
}
/// PHQ-9 量表题目
class PHQ9Questions {
static const List<QuizQuestion> questions = [
QuizQuestion(
questionText: '做事时提不起劲或没有兴趣',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '感到心情低落、沮丧或绝望',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '入睡困难、睡不安稳或睡眠过多',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '感觉疲倦或没有活力',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '食欲不振或吃太多',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '觉得自己很糟,或觉得自己很失败让家人失望',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '对事物专注有困难,例如看报纸或看电视时',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '动作或说话速度缓慢,或与此相反(坐立不安、来回走动)',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
QuizQuestion(
questionText: '有不如死掉或用某种方式伤害自己的念头',
options: ['完全没有', '好几天', '一半以上天数', '几乎每天'],
scores: [0, 1, 2, 3],
),
];
}
/// PHQ-9 结果解读
class PHQ9Interpretation {
/// 分数区间定义
static const List<InterpretationRange> ranges = [
InterpretationRange(
minScore: 0,
maxScore: 4,
level: '无或极轻微抑郁',
color: Color(0xFF27AE60),
description: '你的抑郁症状处于正常范围内。继续保持良好的生活习惯和积极的心态。',
suggestion: '建议保持规律作息,适度运动,与朋友家人保持联系。',
severity: 'none',
),
InterpretationRange(
minScore: 5,
maxScore: 9,
level: '轻度抑郁',
color: Color(0xFFF39C12),
description: '你可能有轻度抑郁症状。建议多关注自己的情绪变化,适当放松。',
suggestion: '建议尝试深呼吸、正念冥想,保持社交活动。如症状持续,考虑寻求专业帮助。',
severity: 'mild',
),
InterpretationRange(
minScore: 10,
maxScore: 14,
level: '中度抑郁',
color: Color(0xFFE67E22),
description: '你可能有中度抑郁症状。建议咨询心理健康专业人士。',
suggestion: '建议寻求心理咨询师或精神科医生的帮助。',
severity: 'moderate',
),
InterpretationRange(
minScore: 15,
maxScore: 19,
level: '中重度抑郁',
color: Color(0xFFE74C3C),
description: '你可能有中重度抑郁症状。建议尽快寻求专业帮助。',
suggestion: '强烈建议预约心理咨询师或精神科医生进行评估。',
severity: 'moderately_severe',
),
InterpretationRange(
minScore: 20,
maxScore: 27,
level: '重度抑郁',
color: Color(0xFFC0392B),
description: '你可能有重度抑郁症状。需要立即寻求专业帮助。',
suggestion: '请立即联系心理健康专业人士或前往医院就诊。',
severity: 'severe',
),
];
/// 根据分数获取解读
static InterpretationRange getInterpretation(int score) {
for (final range in ranges) {
if (score >= range.minScore && score <= range.maxScore) {
return range;
}
}
return ranges.first;
}
}
/// 解读区间
class InterpretationRange {
final int minScore;
final int maxScore;
final String level;
final Color color;
final String description;
final String suggestion;
final String severity;
const InterpretationRange({
required this.minScore,
required this.maxScore,
required this.level,
required this.color,
required this.description,
required this.suggestion,
required this.severity,
});
}
四、测试页面实现
// lib/mental_health/screens/quiz_screen.dart
class QuizScreen extends StatefulWidget {
const QuizScreen({super.key});
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
// 当前测试类型
QuizCategory? _currentCategory;
// 题目列表
List<QuizQuestion> _questions = [];
// 当前题目索引
int _currentIndex = 0;
// 用户答案 {questionIndex: selectedOptionIndex}
Map<int, int> _answers = {};
// 是否完成
bool _isCompleted = false;
// 结果
QuizResult? _result;
Widget build(BuildContext context) {
// 根据状态显示不同内容
if (_currentCategory == null) {
return _buildSelectionView();
} else if (_isCompleted) {
return _buildResultView();
} else {
return _buildQuestionView();
}
}
/// 测试选择页面
Widget _buildSelectionView() {
return Scaffold(
appBar: AppBar(title: const Text('心理测试')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// PHQ-9 卡片
_buildQuizCard(
category: QuizCategory.phq9,
icon: Icons.mood,
),
const SizedBox(height: 16),
// GAD-7 卡片
_buildQuizCard(
category: QuizCategory.gad7,
icon: Icons.psychology,
),
],
),
);
}
/// 测试题目页面
Widget _buildQuestionView() {
final question = _questions[_currentIndex];
return Scaffold(
appBar: AppBar(
title: Text(_currentCategory!.name),
leading: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
setState(() {
_currentCategory = null;
_answers = {};
_currentIndex = 0;
});
},
),
),
body: Column(
children: [
// 进度条
LinearProgressIndicator(
value: (_currentIndex + 1) / _questions.length,
),
// 题目内容
Expanded(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'问题 ${_currentIndex + 1}/${_questions.length}',
style: const TextStyle(
fontSize: 14,
color: Color(0xFF636E72),
),
),
const SizedBox(height: 16),
Text(
question.questionText,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 24),
// 选项列表
...List.generate(question.options.length, (index) {
final isSelected = _answers[_currentIndex] == index;
return _buildOptionItem(
index: index,
text: question.options[index],
isSelected: isSelected,
onTap: () {
setState(() {
_answers[_currentIndex] = index;
});
},
);
}),
],
),
),
),
// 底部导航
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
if (_currentIndex > 0)
TextButton(
onPressed: () {
setState(() => _currentIndex--);
},
child: const Text('上一题'),
),
const Spacer(),
ElevatedButton(
onPressed: _answers.containsKey(_currentIndex)
? () {
if (_currentIndex < _questions.length - 1) {
setState(() => _currentIndex++);
} else {
_calculateResult();
}
}
: null,
child: Text(
_currentIndex < _questions.length - 1 ? '下一题' : '查看结果',
),
),
],
),
),
],
),
);
}
/// 结果页面
Widget _buildResultView() {
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 环形进度
QuizResultWidget(result: _result!),
const SizedBox(height: 24),
// 建议
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFFF3E0),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
const Icon(Icons.lightbulb, color: Color(0xFFFF9800)),
const SizedBox(width: 12),
Expanded(
child: Text(
PHQ9Interpretation.getInterpretation(_result!.totalScore).suggestion,
style: const TextStyle(fontSize: 14),
),
),
],
),
),
],
),
),
),
);
}
Widget _buildQuizCard({
required QuizCategory category,
required IconData icon,
}) {
return Card(
child: InkWell(
onTap: () {
setState(() {
_currentCategory = category;
_questions = category == QuizCategory.phq9
? PHQ9Questions.questions
: GAD7Questions.questions;
_answers = {};
_currentIndex = 0;
_isCompleted = false;
});
},
child: Padding(
padding: const EdgeInsets.all(20),
child: Row(
children: [
Icon(icon, size: 40, color: category.color),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.name,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
Text(
category.description,
style: const TextStyle(color: Color(0xFF636E72)),
),
],
),
),
const Icon(Icons.arrow_forward_ios),
],
),
),
),
);
}
Widget _buildOptionItem({
required int index,
required String text,
required bool isSelected,
required VoidCallback onTap,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: isSelected
? _currentCategory!.color.withOpacity(0.1)
: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? _currentCategory!.color
: const Color(0xFFE0E0E0),
),
),
child: Row(
children: [
Icon(
isSelected ? Icons.check_circle : Icons.circle_outlined,
color: isSelected
? _currentCategory!.color
: const Color(0xFFB2BEC3),
),
const SizedBox(width: 12),
Expanded(child: Text(text)),
],
),
),
),
);
}
void _calculateResult() {
int totalScore = 0;
for (var entry in _answers.entries) {
totalScore += _questions[entry.key].scores[entry.value];
}
setState(() {
_result = QuizResult(
totalScore: totalScore,
maxScore: _currentCategory!.maxScore,
category: _currentCategory!,
);
_isCompleted = true;
});
}
}
五、重要提醒
⚠️ 免责声明
本测试结果仅供参考,不能替代专业医生的诊断。
PHQ-9 量表只是一个筛查工具,不是诊断工具。它可以帮助你了解自己的情绪状态,但最终的诊断和治疗方案需要由专业医生来确定。
如果你有以下情况,请立即就医:
- 有自我伤害的念头
- 测试得分持续很高
- 症状严重影响日常生活
六、我的踩坑记录
坑1:分数计算错误
问题:第9题的分数计算和预期不符。
原因:我没注意到第9题的特殊性,它的选项虽然一样,但含义完全不同。
解决:严格按照量表原文设计题目,不要凭理解修改。
七、大一学生真实学习总结
做这个 PHQ-9 功能让我学到了很多:
- 技术要和专业知识结合:不懂心理学,根本做不出这个功能。
- 科学态度很重要:这个功能涉及心理健康,不能乱来。
- 免责声明不可少:保护用户,也保护自己。
作者:IntMainJhy
创作时间:2026年5月

更多推荐



所有评论(0)