Flutter 框架跨平台鸿蒙开发 - 问答社区应用开发教程

Flutter问答社区应用开发教程

项目简介

问答社区是一款类似知乎、Stack Overflow的问答平台应用,用户可以提问、回答、点赞、收藏,促进知识分享和交流。本项目使用Flutter实现了完整的问答功能、互动系统、个人中心等模块,打造活跃的技术社区。

运行效果图




核心特性

  • 提问功能:发布问题,添加标签
  • 回答功能:回答问题,分享知识
  • 点赞系统:为问题和回答点赞
  • 收藏功能:收藏感兴趣的问题
  • 采纳答案:提问者可采纳最佳答案
  • 标签筛选:按技术标签筛选问题
  • 浏览统计:记录浏览次数
  • 时间显示:智能相对时间显示
  • 个人中心:查看个人数据统计
  • 数据持久化:本地存储所有数据

技术架构

数据模型设计

问题模型
dart 复制代码
class Question {
  final String id;              // 唯一标识
  final String title;           // 问题标题
  final String content;         // 问题详情
  final String authorId;        // 作者ID
  final String authorName;      // 作者名称
  final DateTime createTime;    // 创建时间
  final List<String> tags;      // 标签列表
  int viewCount;                // 浏览次数
  int answerCount;              // 回答数量
  int likeCount;                // 点赞数量
  bool isLiked;                 // 是否已点赞
  bool isCollected;             // 是否已收藏
}

字段说明

  • id:问题唯一标识(使用时间戳)
  • title:问题标题(简明扼要)
  • content:问题详细描述
  • authorId:提问者ID
  • authorName:提问者昵称
  • createTime:发布时间
  • tags:技术标签(最多3个)
  • viewCount:浏览次数
  • answerCount:回答数量
  • likeCount:获赞数量
  • isLiked:当前用户是否点赞
  • isCollected:当前用户是否收藏

标签分类

标签 说明 适用场景
Flutter Flutter框架 组件、布局、状态管理
Dart Dart语言 语法、异步、类型系统
Android Android开发 原生集成、权限
iOS iOS开发 原生集成、配置
Web Web开发 前端技术、浏览器
后端 后端开发 API、数据库、服务器
数据库 数据库 SQL、NoSQL
算法 算法数据结构 算法题、优化
其他 其他技术 未分类问题
回答模型
dart 复制代码
class Answer {
  final String id;              // 唯一标识
  final String questionId;      // 所属问题ID
  final String content;         // 回答内容
  final String authorId;        // 作者ID
  final String authorName;      // 作者名称
  final DateTime createTime;    // 创建时间
  int likeCount;                // 点赞数量
  bool isLiked;                 // 是否已点赞
  bool isAccepted;              // 是否被采纳
}

字段说明

  • id:回答唯一标识
  • questionId:关联的问题ID
  • content:回答内容
  • authorId:回答者ID
  • authorName:回答者昵称
  • createTime:回答时间
  • likeCount:获赞数量
  • isLiked:当前用户是否点赞
  • isAccepted:是否被提问者采纳

状态管理

dart 复制代码
class _QACommunityHomePageState extends State<QACommunityHomePage> {
  int _selectedIndex = 0;              // 当前选中的底部导航索引
  List<Question> questions = [];       // 所有问题列表
  List<Answer> answers = [];           // 所有回答列表
  String currentUserId = 'user_001';   // 当前用户ID
  String currentUserName = '当前用户'; // 当前用户名
  String selectedTag = '全部';         // 当前选中的标签
  
  final List<String> allTags = [       // 所有标签
    '全部', 'Flutter', 'Dart', 'Android', 
    'iOS', 'Web', '后端', '数据库', '算法', '其他',
  ];
}

状态变量说明

  • _selectedIndex:底部导航栏当前页面索引(0-3)
  • questions:所有问题列表
  • answers:所有回答列表
  • currentUserId:当前登录用户ID
  • currentUserName:当前登录用户名
  • selectedTag:当前筛选的标签
  • allTags:可选的标签列表

核心功能实现

1. 数据持久化

dart 复制代码
Future<void> _loadData() async {
  final prefs = await SharedPreferences.getInstance();
  final questionsData = prefs.getStringList('questions') ?? [];
  final answersData = prefs.getStringList('answers') ?? [];
  
  setState(() {
    questions = questionsData
        .map((json) => Question.fromJson(jsonDecode(json)))
        .toList();
    answers = answersData
        .map((json) => Answer.fromJson(jsonDecode(json)))
        .toList();
  });
}

Future<void> _saveData() async {
  final prefs = await SharedPreferences.getInstance();
  final questionsData = questions.map((q) => jsonEncode(q.toJson())).toList();
  final answersData = answers.map((a) => jsonEncode(a.toJson())).toList();
  await prefs.setStringList('questions', questionsData);
  await prefs.setStringList('answers', answersData);
}

存储策略

  • 使用SharedPreferences进行本地存储
  • 问题和回答分别序列化为JSON字符串列表
  • 应用启动时自动加载数据
  • 数据变更时立即保存

JSON序列化

dart 复制代码
Map<String, dynamic> toJson() {
  return {
    'id': id,
    'title': title,
    'content': content,
    'authorId': authorId,
    'authorName': authorName,
    'createTime': createTime.toIso8601String(),
    'tags': tags,
    'viewCount': viewCount,
    'answerCount': answerCount,
    'likeCount': likeCount,
    'isLiked': isLiked,
    'isCollected': isCollected,
  };
}

factory Question.fromJson(Map<String, dynamic> json) {
  return Question(
    id: json['id'],
    title: json['title'],
    content: json['content'],
    authorId: json['authorId'],
    authorName: json['authorName'],
    createTime: DateTime.parse(json['createTime']),
    tags: List<String>.from(json['tags']),
    viewCount: json['viewCount'] ?? 0,
    answerCount: json['answerCount'] ?? 0,
    likeCount: json['likeCount'] ?? 0,
    isLiked: json['isLiked'] ?? false,
    isCollected: json['isCollected'] ?? false,
  );
}

2. 标签筛选

dart 复制代码
final filteredQuestions = selectedTag == '全部'
    ? questions
    : questions.where((q) => q.tags.contains(selectedTag)).toList();

筛选逻辑

  1. 判断是否选择"全部"标签
  2. 如果是,返回所有问题
  3. 否则,筛选包含指定标签的问题

筛选器UI

dart 复制代码
Widget _buildTagFilter() {
  return SizedBox(
    height: 50,
    child: ListView.builder(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      itemCount: allTags.length,
      itemBuilder: (context, index) {
        final tag = allTags[index];
        final isSelected = tag == selectedTag;
        return FilterChip(
          label: Text(tag),
          selected: isSelected,
          onSelected: (selected) {
            setState(() {
              selectedTag = tag;
            });
          },
        );
      },
    ),
  );
}

3. 点赞功能

dart 复制代码
void _toggleQuestionLike(String questionId) {
  setState(() {
    final question = questions.firstWhere((q) => q.id == questionId);
    if (question.isLiked) {
      question.likeCount--;
      question.isLiked = false;
    } else {
      question.likeCount++;
      question.isLiked = true;
    }
  });
  _saveData();
}

void _toggleAnswerLike(String answerId) {
  setState(() {
    final answer = answers.firstWhere((a) => a.id == answerId);
    if (answer.isLiked) {
      answer.likeCount--;
      answer.isLiked = false;
    } else {
      answer.likeCount++;
      answer.isLiked = true;
    }
  });
  _saveData();
}

点赞逻辑

  1. 查找对应的问题或回答
  2. 判断当前点赞状态
  3. 如果已点赞,取消点赞并减少计数
  4. 如果未点赞,添加点赞并增加计数
  5. 更新UI并保存数据

4. 收藏功能

dart 复制代码
void _toggleQuestionCollect(String questionId) {
  setState(() {
    final question = questions.firstWhere((q) => q.id == questionId);
    question.isCollected = !question.isCollected;
  });
  _saveData();
}

收藏逻辑

  • 切换收藏状态
  • 更新UI
  • 保存数据

5. 采纳答案

dart 复制代码
void _acceptAnswer(String answerId) {
  setState(() {
    final answer = answers.firstWhere((a) => a.id == answerId);
    // 取消其他答案的采纳状态
    for (final a in answers.where((a) => a.questionId == answer.questionId)) {
      a.isAccepted = false;
    }
    answer.isAccepted = true;
  });
  _saveData();
}

采纳逻辑

  1. 查找要采纳的答案
  2. 取消同一问题下其他答案的采纳状态
  3. 设置当前答案为已采纳
  4. 更新UI并保存数据

采纳规则

  • 只有提问者可以采纳答案
  • 每个问题只能采纳一个答案
  • 采纳后可以更改

6. 提问对话框

dart 复制代码
Future<void> _showAskQuestionDialog() async {
  final titleController = TextEditingController();
  final contentController = TextEditingController();
  final selectedTags = <String>[];

  final result = await showDialog<Question>(
    context: context,
    builder: (context) => StatefulBuilder(
      builder: (context, setState) => AlertDialog(
        title: const Text('提问'),
        content: SingleChildScrollView(
          child: Column(
            children: [
              // 标题输入
              TextField(
                controller: titleController,
                decoration: InputDecoration(
                  labelText: '问题标题',
                  hintText: '简明扼要地描述你的问题',
                ),
              ),
              // 详情输入
              TextField(
                controller: contentController,
                decoration: InputDecoration(
                  labelText: '问题详情',
                  hintText: '详细描述你遇到的问题',
                ),
                maxLines: 5,
              ),
              // 标签选择
              Wrap(
                children: allTags.where((tag) => tag != '全部').map((tag) {
                  return FilterChip(
                    label: Text(tag),
                    selected: selectedTags.contains(tag),
                    onSelected: (selected) {
                      setState(() {
                        if (selected && selectedTags.length < 3) {
                          selectedTags.add(tag);
                        } else {
                          selectedTags.remove(tag);
                        }
                      });
                    },
                  );
                }).toList(),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}

对话框特性

  • 使用StatefulBuilder实现对话框内状态更新
  • 标题和详情文本输入
  • FilterChip多选标签(最多3个)
  • 数据验证
  • 返回Question对象

数据验证

dart 复制代码
if (titleController.text.isEmpty) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('请输入问题标题')),
  );
  return;
}
if (contentController.text.isEmpty) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('请输入问题详情')),
  );
  return;
}
if (selectedTags.isEmpty) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('请至少选择一个标签')),
  );
  return;
}

7. 回答排序

dart 复制代码
final sortedAnswers = List<Answer>.from(widget.answers)
  ..sort((a, b) {
    if (a.isAccepted && !b.isAccepted) return -1;
    if (!a.isAccepted && b.isAccepted) return 1;
    return b.likeCount.compareTo(a.likeCount);
  });

排序规则

  1. 已采纳的答案排在最前面
  2. 其他答案按点赞数降序排列
  3. 点赞数相同则保持原顺序

8. 时间格式化

dart 复制代码
String _formatTime(DateTime dateTime) {
  final now = DateTime.now();
  final difference = now.difference(dateTime);

  if (difference.inMinutes < 1) {
    return '刚刚';
  } else if (difference.inMinutes < 60) {
    return '${difference.inMinutes}分钟前';
  } else if (difference.inHours < 24) {
    return '${difference.inHours}小时前';
  } else if (difference.inDays < 7) {
    return '${difference.inDays}天前';
  } else {
    return '${dateTime.month}月${dateTime.day}日';
  }
}

格式化规则

  • 1分钟内:刚刚
  • 1小时内:X分钟前
  • 24小时内:X小时前
  • 7天内:X天前
  • 7天以上:M月D日

9. 浏览统计

dart 复制代码
void _navigateToQuestionDetail(Question question) {
  question.viewCount++;
  _saveData();
  
  Navigator.push(context, MaterialPageRoute(...));
}

统计逻辑

  • 进入问题详情页时浏览次数+1
  • 立即保存数据
  • 显示在问题卡片上

UI组件设计

1. 问题卡片

dart 复制代码
Widget _buildQuestionCard(Question question) {
  return Card(
    child: InkWell(
      onTap: () => _navigateToQuestionDetail(question),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题
            Text(
              question.title,
              style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            // 内容预览
            Text(
              question.content,
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
            // 标签
            Wrap(
              children: question.tags.map((tag) {
                return Chip(label: Text(tag));
              }).toList(),
            ),
            // 作者和统计信息
            Row(
              children: [
                CircleAvatar(child: Text(question.authorName[0])),
                Text(question.authorName),
                Text(_formatTime(question.createTime)),
                Spacer(),
                Icon(Icons.visibility),
                Text('${question.viewCount}'),
                Icon(Icons.comment),
                Text('${question.answerCount}'),
                Icon(Icons.thumb_up),
                Text('${question.likeCount}'),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

卡片结构

复制代码
┌────────────────────────────────────┐
│ Flutter如何实现状态管理?          │
│ 我是Flutter新手,想了解一下...     │
│ [Flutter] [状态管理]               │
│ 👤 小明  2小时前  👁128 💬3 👍15  │
└────────────────────────────────────┘

2. 回答卡片

dart 复制代码
Widget _buildAnswerCard(Answer answer) {
  final isAuthor = answer.authorId == widget.question.authorId;
  final canAccept = widget.question.authorId == widget.currentUserId;

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          Row(
            children: [
              CircleAvatar(child: Text(answer.authorName[0])),
              Text(answer.authorName),
              if (isAuthor)
                Container(child: Text('作者')),
              if (answer.isAccepted)
                Container(child: Row([
                  Icon(Icons.check_circle),
                  Text('已采纳'),
                ])),
              Spacer(),
              if (canAccept && !answer.isAccepted)
                TextButton(
                  onPressed: () => widget.onAcceptAnswer(answer.id),
                  child: Text('采纳'),
                ),
            ],
          ),
          Text(answer.content),
          Row(
            children: [
              IconButton(
                icon: Icon(answer.isLiked ? Icons.thumb_up : Icons.thumb_up_outlined),
                onPressed: () => widget.onToggleAnswerLike(answer.id),
              ),
              Text('${answer.likeCount}'),
            ],
          ),
        ],
      ),
    ),
  );
}

卡片特性

  • 显示作者标识
  • 显示采纳状态
  • 提问者可采纳答案
  • 点赞功能
  • 相对时间显示

3. 个人中心卡片

dart 复制代码
Widget _buildProfilePage() {
  return Column(
    children: [
      // 用户信息卡片
      Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.deepPurple.shade400, Colors.deepPurple.shade600],
          ),
        ),
        child: Column(
          children: [
            CircleAvatar(radius: 40, child: Text(currentUserName[0])),
            Text(currentUserName, style: TextStyle(color: Colors.white)),
            Text('ID: $currentUserId', style: TextStyle(color: Colors.white70)),
          ],
        ),
      ),
      // 统计卡片
      Row(
        children: [
          _buildStatCard('提问', '$myQuestionsCount', Icons.question_answer),
          _buildStatCard('回答', '$myAnswersCount', Icons.comment),
          _buildStatCard('获赞', '$totalLikes', Icons.thumb_up),
        ],
      ),
    ],
  );
}

个人中心布局

复制代码
┌─────────────────────────┐
│      渐变紫色背景        │
│        👤               │
│      当前用户            │
│   ID: user_001         │
└─────────────────────────┘
┌───────┬───────┬───────┐
│ 💬 5  │ 💭 12 │ 👍 38 │
│ 提问  │ 回答  │ 获赞  │
└───────┴───────┴───────┘

4. 回答输入框

dart 复制代码
Widget _buildAnswerInput() {
  return Container(
    padding: EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: Colors.white,
      boxShadow: [BoxShadow(...)],
    ),
    child: Row(
      children: [
        Expanded(
          child: TextField(
            controller: _answerController,
            decoration: InputDecoration(
              hintText: '写下你的回答...',
              border: OutlineInputBorder(),
            ),
            maxLines: null,
          ),
        ),
        IconButton(
          icon: Icon(Icons.send),
          onPressed: () {
            // 发送回答
          },
        ),
      ],
    ),
  );
}

输入框特性

  • 固定在底部
  • 多行输入
  • 发送按钮
  • 阴影效果

功能扩展建议

1. 搜索功能

dart 复制代码
class SearchDelegate extends SearchDelegate<Question?> {
  final List<Question> questions;
  
  SearchDelegate(this.questions);
  
  @override
  List<Widget> buildActions(BuildContext context) {
    return [
      IconButton(
        icon: Icon(Icons.clear),
        onPressed: () {
          query = '';
        },
      ),
    ];
  }
  
  @override
  Widget buildResults(BuildContext context) {
    final results = questions.where((q) {
      return q.title.toLowerCase().contains(query.toLowerCase()) ||
             q.content.toLowerCase().contains(query.toLowerCase());
    }).toList();
    
    return ListView.builder(
      itemCount: results.length,
      itemBuilder: (context, index) {
        return QuestionCard(question: results[index]);
      },
    );
  }
  
  @override
  Widget buildSuggestions(BuildContext context) {
    final suggestions = questions.where((q) {
      return q.title.toLowerCase().contains(query.toLowerCase());
    }).take(5).toList();
    
    return ListView.builder(
      itemCount: suggestions.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(suggestions[index].title),
          onTap: () {
            close(context, suggestions[index]);
          },
        );
      },
    );
  }
}

搜索功能

  • 标题和内容搜索
  • 实时搜索建议
  • 搜索历史
  • 热门搜索

2. 评论功能

dart 复制代码
class Comment {
  final String id;
  final String answerId;
  final String content;
  final String authorId;
  final String authorName;
  final DateTime createTime;
  
  Comment({
    required this.id,
    required this.answerId,
    required this.content,
    required this.authorId,
    required this.authorName,
    required this.createTime,
  });
}

Widget _buildCommentSection(String answerId) {
  final comments = allComments.where((c) => c.answerId == answerId).toList();
  
  return Column(
    children: [
      ...comments.map((comment) => ListTile(
        leading: CircleAvatar(child: Text(comment.authorName[0])),
        title: Text(comment.authorName),
        subtitle: Text(comment.content),
        trailing: Text(_formatTime(comment.createTime)),
      )),
      TextField(
        decoration: InputDecoration(
          hintText: '写评论...',
          suffixIcon: IconButton(
            icon: Icon(Icons.send),
            onPressed: () {
              // 发送评论
            },
          ),
        ),
      ),
    ],
  );
}

评论功能

  • 回答下的评论
  • 评论回复
  • 评论点赞
  • 评论折叠

3. 关注系统

dart 复制代码
class UserFollow {
  final String followerId;
  final String followingId;
  final DateTime followTime;
  
  UserFollow({
    required this.followerId,
    required this.followingId,
    required this.followTime,
  });
}

class FollowManager {
  List<UserFollow> follows = [];
  
  bool isFollowing(String userId, String targetUserId) {
    return follows.any((f) => 
      f.followerId == userId && f.followingId == targetUserId
    );
  }
  
  void follow(String userId, String targetUserId) {
    if (!isFollowing(userId, targetUserId)) {
      follows.add(UserFollow(
        followerId: userId,
        followingId: targetUserId,
        followTime: DateTime.now(),
      ));
    }
  }
  
  void unfollow(String userId, String targetUserId) {
    follows.removeWhere((f) => 
      f.followerId == userId && f.followingId == targetUserId
    );
  }
  
  List<String> getFollowers(String userId) {
    return follows
        .where((f) => f.followingId == userId)
        .map((f) => f.followerId)
        .toList();
  }
  
  List<String> getFollowing(String userId) {
    return follows
        .where((f) => f.followerId == userId)
        .map((f) => f.followingId)
        .toList();
  }
}

关注功能

  • 关注用户
  • 粉丝列表
  • 关注列表
  • 关注动态

4. 通知系统

dart 复制代码
class Notification {
  final String id;
  final String userId;
  final String type;  // 'like', 'answer', 'comment', 'accept'
  final String content;
  final String? relatedId;
  final DateTime createTime;
  bool isRead;
  
  Notification({
    required this.id,
    required this.userId,
    required this.type,
    required this.content,
    this.relatedId,
    required this.createTime,
    this.isRead = false,
  });
}

class NotificationManager {
  List<Notification> notifications = [];
  
  void addNotification(Notification notification) {
    notifications.insert(0, notification);
  }
  
  void markAsRead(String notificationId) {
    final notification = notifications.firstWhere((n) => n.id == notificationId);
    notification.isRead = true;
  }
  
  void markAllAsRead(String userId) {
    for (final notification in notifications.where((n) => n.userId == userId)) {
      notification.isRead = true;
    }
  }
  
  int getUnreadCount(String userId) {
    return notifications.where((n) => 
      n.userId == userId && !n.isRead
    ).length;
  }
}

Widget _buildNotificationBadge(int unreadCount) {
  return Stack(
    children: [
      Icon(Icons.notifications),
      if (unreadCount > 0)
        Positioned(
          right: 0,
          top: 0,
          child: Container(
            padding: EdgeInsets.all(2),
            decoration: BoxDecoration(
              color: Colors.red,
              shape: BoxShape.circle,
            ),
            child: Text(
              '$unreadCount',
              style: TextStyle(color: Colors.white, fontSize: 10),
            ),
          ),
        ),
    ],
  );
}

通知类型

  • 回答通知
  • 点赞通知
  • 评论通知
  • 采纳通知
  • 关注通知

5. 排行榜

dart 复制代码
class Leaderboard {
  List<UserStats> getUserRanking() {
    final userStats = <String, UserStats>{};
    
    // 统计每个用户的数据
    for (final question in questions) {
      final stats = userStats.putIfAbsent(
        question.authorId,
        () => UserStats(userId: question.authorId, userName: question.authorName),
      );
      stats.questionCount++;
      stats.totalLikes += question.likeCount;
    }
    
    for (final answer in answers) {
      final stats = userStats.putIfAbsent(
        answer.authorId,
        () => UserStats(userId: answer.authorId, userName: answer.authorName),
      );
      stats.answerCount++;
      stats.totalLikes += answer.likeCount;
      if (answer.isAccepted) {
        stats.acceptedCount++;
      }
    }
    
    // 计算积分并排序
    final ranking = userStats.values.toList();
    for (final stats in ranking) {
      stats.score = stats.questionCount * 5 + 
                    stats.answerCount * 10 + 
                    stats.acceptedCount * 20 + 
                    stats.totalLikes * 2;
    }
    
    ranking.sort((a, b) => b.score.compareTo(a.score));
    return ranking;
  }
}

class UserStats {
  final String userId;
  final String userName;
  int questionCount = 0;
  int answerCount = 0;
  int acceptedCount = 0;
  int totalLikes = 0;
  int score = 0;
  
  UserStats({
    required this.userId,
    required this.userName,
  });
}

Widget _buildLeaderboardPage() {
  final ranking = leaderboard.getUserRanking();
  
  return ListView.builder(
    itemCount: ranking.length,
    itemBuilder: (context, index) {
      final stats = ranking[index];
      return ListTile(
        leading: CircleAvatar(
          child: Text('${index + 1}'),
          backgroundColor: index < 3 ? Colors.amber : Colors.grey,
        ),
        title: Text(stats.userName),
        subtitle: Text('提问${stats.questionCount} · 回答${stats.answerCount} · 采纳${stats.acceptedCount}'),
        trailing: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('${stats.score}', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
            Text('积分', style: TextStyle(fontSize: 12)),
          ],
        ),
      );
    },
  );
}

积分规则

  • 提问:5分
  • 回答:10分
  • 被采纳:20分
  • 获赞:2分

6. 话题广场

dart 复制代码
class Topic {
  final String id;
  final String name;
  final String description;
  final String icon;
  int followCount;
  int questionCount;
  
  Topic({
    required this.id,
    required this.name,
    required this.description,
    required this.icon,
    this.followCount = 0,
    this.questionCount = 0,
  });
}

class TopicManager {
  final List<Topic> topics = [
    Topic(
      id: '1',
      name: 'Flutter开发',
      description: 'Flutter框架相关问题讨论',
      icon: '📱',
      followCount: 1250,
      questionCount: 3420,
    ),
    Topic(
      id: '2',
      name: 'Dart语言',
      description: 'Dart语言学习和使用',
      icon: '🎯',
      followCount: 890,
      questionCount: 1560,
    ),
    // 更多话题...
  ];
  
  List<Question> getTopicQuestions(String topicId) {
    return questions.where((q) {
      final topic = topics.firstWhere((t) => t.id == topicId);
      return q.tags.contains(topic.name);
    }).toList();
  }
}

Widget _buildTopicCard(Topic topic) {
  return Card(
    child: ListTile(
      leading: CircleAvatar(
        child: Text(topic.icon, style: TextStyle(fontSize: 24)),
        backgroundColor: Colors.transparent,
      ),
      title: Text(topic.name),
      subtitle: Text(topic.description),
      trailing: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('${topic.questionCount}', style: TextStyle(fontWeight: FontWeight.bold)),
          Text('问题', style: TextStyle(fontSize: 12)),
        ],
      ),
      onTap: () {
        // 进入话题详情
      },
    ),
  );
}

话题功能

  • 话题列表
  • 话题关注
  • 话题问题
  • 热门话题

7. 草稿箱

dart 复制代码
class Draft {
  final String id;
  final String type;  // 'question' or 'answer'
  final String? questionId;
  final String title;
  final String content;
  final List<String> tags;
  final DateTime saveTime;
  
  Draft({
    required this.id,
    required this.type,
    this.questionId,
    required this.title,
    required this.content,
    required this.tags,
    required this.saveTime,
  });
}

class DraftManager {
  List<Draft> drafts = [];
  
  Future<void> saveDraft(Draft draft) async {
    final existingIndex = drafts.indexWhere((d) => d.id == draft.id);
    if (existingIndex >= 0) {
      drafts[existingIndex] = draft;
    } else {
      drafts.add(draft);
    }
    await _saveDrafts();
  }
  
  Future<void> deleteDraft(String draftId) async {
    drafts.removeWhere((d) => d.id == draftId);
    await _saveDrafts();
  }
  
  Draft? getDraft(String draftId) {
    try {
      return drafts.firstWhere((d) => d.id == draftId);
    } catch (e) {
      return null;
    }
  }
}

Widget _buildDraftsList() {
  return ListView.builder(
    itemCount: draftManager.drafts.length,
    itemBuilder: (context, index) {
      final draft = draftManager.drafts[index];
      return ListTile(
        leading: Icon(
          draft.type == 'question' ? Icons.question_answer : Icons.comment,
        ),
        title: Text(draft.title.isEmpty ? '未命名草稿' : draft.title),
        subtitle: Text(_formatTime(draft.saveTime)),
        trailing: IconButton(
          icon: Icon(Icons.delete),
          onPressed: () => draftManager.deleteDraft(draft.id),
        ),
        onTap: () {
          // 恢复草稿
        },
      );
    },
  );
}

草稿功能

  • 自动保存草稿
  • 草稿列表
  • 恢复草稿
  • 删除草稿

8. 举报系统

dart 复制代码
class Report {
  final String id;
  final String reporterId;
  final String targetType;  // 'question' or 'answer'
  final String targetId;
  final String reason;
  final String? description;
  final DateTime createTime;
  String status;  // 'pending', 'approved', 'rejected'
  
  Report({
    required this.id,
    required this.reporterId,
    required this.targetType,
    required this.targetId,
    required this.reason,
    this.description,
    required this.createTime,
    this.status = 'pending',
  });
}

Future<void> _showReportDialog(String targetType, String targetId) async {
  final reasons = [
    '垃圾广告',
    '违法违规',
    '不友善内容',
    '抄袭',
    '其他',
  ];
  
  String? selectedReason;
  final descriptionController = TextEditingController();
  
  await showDialog(
    context: context,
    builder: (context) => StatefulBuilder(
      builder: (context, setState) => AlertDialog(
        title: Text('举报'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            DropdownButtonFormField<String>(
              decoration: InputDecoration(labelText: '举报原因'),
              items: reasons.map((reason) {
                return DropdownMenuItem(value: reason, child: Text(reason));
              }).toList(),
              onChanged: (value) {
                setState(() {
                  selectedReason = value;
                });
              },
            ),
            TextField(
              controller: descriptionController,
              decoration: InputDecoration(
                labelText: '详细说明(可选)',
              ),
              maxLines: 3,
            ),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: Text('取消'),
          ),
          TextButton(
            onPressed: () {
              if (selectedReason == null) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('请选择举报原因')),
                );
                return;
              }
              
              final report = Report(
                id: DateTime.now().millisecondsSinceEpoch.toString(),
                reporterId: currentUserId,
                targetType: targetType,
                targetId: targetId,
                reason: selectedReason!,
                description: descriptionController.text,
                createTime: DateTime.now(),
              );
              
              // 提交举报
              Navigator.pop(context);
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('举报已提交')),
              );
            },
            child: Text('提交'),
          ),
        ],
      ),
    ),
  );
}

举报功能

  • 举报问题
  • 举报回答
  • 举报原因选择
  • 举报处理

数据流程图

提问
查看问题
回答
点赞
收藏
采纳
应用启动
加载本地数据
显示首页
用户操作
填写问题
选择标签
发布问题
更新列表
进入详情
浏览次数+1
显示回答
用户操作
输入回答
发布回答
更新回答列表
切换点赞状态
更新计数
切换收藏状态
设置采纳状态

状态管理流程

用户提问
点击问题
用户回答
用户点赞
作者采纳
初始化
加载数据
空闲状态
提问
填写表单
验证数据
保存问题
更新UI
查看详情
增加浏览
显示详情
回答问题
保存回答
点赞
切换状态
采纳
更新状态

性能优化

1. 列表优化

dart 复制代码
ListView.builder(
  itemCount: questions.length,
  itemBuilder: (context, index) {
    return QuestionCard(question: questions[index]);
  },
  cacheExtent: 500,
)

2. 图片缓存

dart 复制代码
import 'package:cached_network_image/cached_network_image.dart';

Widget _buildUserAvatar(String imageUrl) {
  return CachedNetworkImage(
    imageUrl: imageUrl,
    placeholder: (context, url) => CircularProgressIndicator(),
    errorWidget: (context, url, error) => Icon(Icons.person),
    imageBuilder: (context, imageProvider) => CircleAvatar(
      backgroundImage: imageProvider,
    ),
  );
}

3. 分页加载

dart 复制代码
class PaginatedQuestionList extends StatefulWidget {
  @override
  State<PaginatedQuestionList> createState() => _PaginatedQuestionListState();
}

class _PaginatedQuestionListState extends State<PaginatedQuestionList> {
  final ScrollController _scrollController = ScrollController();
  List<Question> displayedQuestions = [];
  int currentPage = 0;
  final int pageSize = 20;
  bool isLoading = false;
  
  @override
  void initState() {
    super.initState();
    _loadPage();
    _scrollController.addListener(_onScroll);
  }
  
  void _loadPage() {
    if (isLoading) return;
    
    setState(() {
      isLoading = true;
    });
    
    final start = currentPage * pageSize;
    final end = (start + pageSize).clamp(0, allQuestions.length);
    
    setState(() {
      displayedQuestions.addAll(allQuestions.sublist(start, end));
      currentPage++;
      isLoading = false;
    });
  }
  
  void _onScroll() {
    if (_scrollController.position.pixels ==
        _scrollController.position.maxScrollExtent) {
      _loadPage();
    }
  }
}

测试建议

1. 单元测试

dart 复制代码
import 'package:flutter_test/flutter_test.dart';

void main() {
  group('问题模型测试', () {
    test('JSON序列化', () {
      final question = Question(
        id: '1',
        title: '测试问题',
        content: '测试内容',
        authorId: 'user_001',
        authorName: '测试用户',
        createTime: DateTime.now(),
        tags: ['Flutter'],
      );
      
      final json = question.toJson();
      final decoded = Question.fromJson(json);
      
      expect(decoded.id, question.id);
      expect(decoded.title, question.title);
      expect(decoded.tags, question.tags);
    });
  });
  
  group('点赞功能测试', () {
    test('点赞计数', () {
      final question = Question(
        id: '1',
        title: '测试',
        content: '测试',
        authorId: 'user_001',
        authorName: '用户',
        createTime: DateTime.now(),
        tags: ['Flutter'],
        likeCount: 0,
        isLiked: false,
      );
      
      // 点赞
      question.likeCount++;
      question.isLiked = true;
      expect(question.likeCount, 1);
      expect(question.isLiked, true);
      
      // 取消点赞
      question.likeCount--;
      question.isLiked = false;
      expect(question.likeCount, 0);
      expect(question.isLiked, false);
    });
  });
}

2. Widget测试

dart 复制代码
void main() {
  testWidgets('问题卡片显示测试', (WidgetTester tester) async {
    final question = Question(
      id: '1',
      title: 'Flutter如何实现状态管理?',
      content: '我是Flutter新手',
      authorId: 'user_001',
      authorName: '小明',
      createTime: DateTime.now(),
      tags: ['Flutter'],
      viewCount: 128,
      answerCount: 3,
      likeCount: 15,
    );
    
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: QuestionCard(question: question),
        ),
      ),
    );
    
    expect(find.text('Flutter如何实现状态管理?'), findsOneWidget);
    expect(find.text('小明'), findsOneWidget);
    expect(find.text('128'), findsOneWidget);
  });
}

3. 集成测试

dart 复制代码
void main() {
  testWidgets('完整提问流程测试', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    
    // 点击提问按钮
    await tester.tap(find.byType(FloatingActionButton));
    await tester.pumpAndSettle();
    
    // 输入标题
    await tester.enterText(find.byType(TextField).first, '测试问题');
    
    // 输入内容
    await tester.enterText(find.byType(TextField).at(1), '测试内容');
    
    // 选择标签
    await tester.tap(find.text('Flutter'));
    await tester.pumpAndSettle();
    
    // 发布
    await tester.tap(find.text('发布'));
    await tester.pumpAndSettle();
    
    // 验证问题已添加
    expect(find.text('测试问题'), findsWidgets);
  });
}

最佳实践

1. 代码组织

dart 复制代码
// 将常量提取到单独文件
class AppConstants {
  static const int maxTagsPerQuestion = 3;
  static const int pageSize = 20;
  static const List<String> allTags = [
    '全部', 'Flutter', 'Dart', 'Android', 
    'iOS', 'Web', '后端', '数据库', '算法', '其他',
  ];
}

// 使用扩展方法
extension QuestionExtension on Question {
  bool hasTag(String tag) => tags.contains(tag);
  
  bool isHot() => viewCount > 100 || likeCount > 20;
  
  String get summary {
    return content.length > 100 
        ? '${content.substring(0, 100)}...' 
        : content;
  }
}

2. 错误处理

dart 复制代码
class ErrorHandler {
  static void handle(BuildContext context, dynamic error) {
    String message = '操作失败';
    
    if (error is FormatException) {
      message = '数据格式错误';
    } else if (error is Exception) {
      message = error.toString();
    }
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
      ),
    );
  }
}

3. 可维护性

dart 复制代码
// 使用命名参数
Question({
  required this.id,
  required this.title,
  required this.content,
  required this.authorId,
  required this.authorName,
  required this.createTime,
  required this.tags,
  this.viewCount = 0,
  this.answerCount = 0,
  this.likeCount = 0,
  this.isLiked = false,
  this.isCollected = false,
});

// 添加文档注释
/// 切换问题的点赞状态
/// 
/// [questionId] 问题ID
/// 
/// 如果已点赞则取消,未点赞则添加
void toggleQuestionLike(String questionId) {
  // ...
}

项目结构

复制代码
lib/
├── main.dart
├── models/
│   ├── question.dart
│   ├── answer.dart
│   ├── comment.dart
│   └── user.dart
├── screens/
│   ├── home_page.dart
│   ├── question_detail_page.dart
│   ├── my_questions_page.dart
│   ├── collections_page.dart
│   └── profile_page.dart
├── widgets/
│   ├── question_card.dart
│   ├── answer_card.dart
│   ├── tag_filter.dart
│   └── stat_card.dart
├── services/
│   ├── storage_service.dart
│   ├── notification_service.dart
│   └── search_service.dart
└── utils/
    ├── constants.dart
    ├── time_formatter.dart
    └── error_handler.dart

依赖包

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2      # 本地存储
  
  # 可选扩展
  cached_network_image: ^3.3.1    # 图片缓存
  flutter_markdown: ^0.6.18       # Markdown渲染
  share_plus: ^7.2.2              # 分享功能
  url_launcher: ^6.2.5            # 链接跳转

社区运营建议

1. 内容质量

  • 鼓励详细的问题描述
  • 提供问题模板
  • 标记优质回答
  • 定期整理精华内容

2. 用户激励

  • 积分系统
  • 等级制度
  • 徽章奖励
  • 排行榜

3. 社区规范

  • 发布规则
  • 行为准则
  • 举报机制
  • 内容审核

4. 活跃度提升

  • 每日签到
  • 话题活动
  • 问答挑战
  • 专家答疑

总结

本项目实现了一个功能完整的问答社区应用,涵盖以下核心技术:

  1. 数据模型:Question和Answer模型设计
  2. 互动系统:点赞、收藏、采纳功能
  3. 标签筛选:多标签分类和筛选
  4. 时间格式化:智能相对时间显示
  5. 数据统计:浏览、回答、点赞统计
  6. 数据持久化:SharedPreferences本地存储
  7. UI设计:卡片布局、底部导航、对话框
  8. 用户体验:空状态、加载状态、成功反馈

通过本教程,你可以学习到:

  • 复杂数据模型的设计和关联
  • 多页面导航和状态管理
  • 列表筛选和排序
  • 互动功能实现
  • 本地数据持久化
  • Material 3设计规范
  • 社区应用开发模式

这个项目可以作为学习Flutter应用开发的实用案例,通过扩展搜索、评论、关注、通知等功能,可以打造更加完善的技术社区平台。

问答社区运营指南

内容分类

分类 适合问题 示例
技术问答 编程问题、bug求助 "Flutter如何实现..."
经验分享 最佳实践、踩坑经验 "我是如何优化..."
学习路线 学习建议、资源推荐 "Flutter学习路线"
项目讨论 架构设计、技术选型 "如何设计..."

提问技巧

  1. 标题清晰:简明扼要描述问题
  2. 详细描述:提供足够的上下文
  3. 代码示例:附上相关代码
  4. 错误信息:贴出完整错误
  5. 尝试过的方法:说明已尝试的解决方案

回答技巧

  1. 理解问题:确保理解提问者的需求
  2. 提供方案:给出具体可行的解决方案
  3. 代码示例:提供可运行的代码
  4. 解释原理:说明为什么这样做
  5. 扩展阅读:推荐相关资料

社区氛围

  • 友善互助
  • 尊重他人
  • 分享知识
  • 共同进步

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
C_心欲无痕2 小时前
Next.js 路由系统对比:Pages Router vs App Router
开发语言·前端·javascript
LawrenceLan2 小时前
Flutter 零基础入门(二十二):Text 文本组件与样式系统
开发语言·前端·flutter·dart
kylezhao20192 小时前
C# 各种类型转换深入剖析
开发语言·c#
hxjhnct2 小时前
JavaScript 的 new会发生什么
开发语言·javascript
哈哈你是真的厉害2 小时前
基础入门 React Native 鸿蒙跨平台开发:Calendar 日历
react native·react.js·harmonyos
少控科技2 小时前
QT进阶日记004
开发语言·qt
狗都不学爬虫_2 小时前
JS逆向 - 最新版某某安全中心滑块验证(wasm设备指纹)
javascript·爬虫·python·网络爬虫·wasm
阿杰 AJie2 小时前
Lambda 表达式大全
开发语言·windows·python
格鸰爱童话2 小时前
python基础总结
开发语言·python