Flutter笑话大全应用开发教程

项目简介

这是一款轻松娱乐的笑话应用,收录了多种类型的笑话,包括冷笑话、脑筋急转弯、幽默段子、爆笑笑话、经典笑话和搞笑对话。应用提供分类浏览、随机笑话、收藏功能和分享功能,界面简洁友好,让用户随时随地开心一刻。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心特性

  • 分类浏览:6大笑话分类,20+精选笑话
  • 随机笑话:一键获取随机笑话,惊喜不断
  • 收藏功能:收藏喜欢的笑话,随时回看
  • 复制分享:一键复制笑话内容,分享给朋友
  • 点赞统计:显示笑话受欢迎程度
  • 详情页面:完整展示笑话内容
  • 渐变设计:橙色主题,温暖活泼
  • 流畅交互:卡片式布局,操作便捷

技术栈

  • Flutter 3.x
  • Material Design 3
  • 状态管理(setState)
  • 剪贴板操作
  • 列表过滤

项目架构

JokeHomePage

JokeListPage

RandomJokePage

FavoritesPage

JokeDetailPage

Joke Model

数据模型设计

Joke(笑话模型)

class Joke {
  final int id;              // 笑话ID
  final String title;        // 标题
  final String content;      // 内容
  final String category;     // 分类
  final int likes;           // 点赞数
  bool isFavorite;           // 是否收藏
  
  Joke({
    required this.id,
    required this.title,
    required this.content,
    required this.category,
    this.likes = 0,
    this.isFavorite = false,
  });
}

设计要点

  • ID用于唯一标识
  • 标题和内容分离,便于列表展示
  • 分类用于筛选
  • 点赞数展示受欢迎程度
  • isFavorite标记收藏状态

笑话分类

final List<String> _categories = [
  '全部',
  '冷笑话',
  '脑筋急转弯',
  '幽默段子',
  '爆笑笑话',
  '经典笑话',
  '搞笑对话'
];

分类说明

  • 冷笑话:需要思考才能理解的笑话
  • 脑筋急转弯:考验思维的问答笑话
  • 幽默段子:生活中的幽默小故事
  • 爆笑笑话:让人捧腹大笑的笑话
  • 经典笑话:流传已久的经典笑话
  • 搞笑对话:有趣的对话场景

核心功能实现

1. 分类筛选

使用计算属性过滤笑话列表。

List<Joke> get _filteredJokes {
  if (_selectedCategory == '全部') {
    return _jokes;
  }
  return _jokes.where((joke) => joke.category == _selectedCategory).toList();
}

分类标签栏

Widget _buildCategoryTabs() {
  return Container(
    height: 50,
    child: ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: categories.length,
      itemBuilder: (context, index) {
        final category = categories[index];
        final isSelected = category == selectedCategory;
        return GestureDetector(
          onTap: () => onCategoryChanged(category),
          child: Container(
            decoration: BoxDecoration(
              color: isSelected ? Colors.orange : Colors.grey.shade200,
              borderRadius: BorderRadius.circular(20),
            ),
            child: Text(
              category,
              style: TextStyle(
                color: isSelected ? Colors.white : Colors.grey.shade700,
                fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
              ),
            ),
          ),
        );
      },
    ),
  );
}

2. 笑话卡片设计

Widget _buildJokeCard(BuildContext context, Joke joke) {
  return Card(
    child: InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => JokeDetailPage(joke: joke),
          ),
        );
      },
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 分类标签和点赞数
            Row(
              children: [
                Container(
                  padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: Colors.orange.shade100,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(joke.category),
                ),
                Spacer(),
                Icon(Icons.thumb_up_outlined),
                Text('${joke.likes}'),
              ],
            ),
            // 标题
            Text(
              joke.title,
              style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
            ),
            // 内容预览
            Text(
              joke.content,
              maxLines: 3,
              overflow: TextOverflow.ellipsis,
            ),
            // 操作按钮
            Row(
              children: [
                TextButton.icon(
                  onPressed: () => onToggleFavorite(joke),
                  icon: Icon(joke.isFavorite ? Icons.favorite : Icons.favorite_border),
                  label: Text(joke.isFavorite ? '已收藏' : '收藏'),
                ),
                TextButton.icon(
                  onPressed: () {
                    Clipboard.setData(ClipboardData(text: joke.content));
                  },
                  icon: Icon(Icons.copy),
                  label: Text('复制'),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

卡片特点

  • 分类标签醒目
  • 点赞数展示受欢迎程度
  • 内容预览3行,超出显示省略号
  • 收藏和复制按钮便捷操作

3. 随机笑话功能

使用Random类随机选择笑话。

class _RandomJokePageState extends State<RandomJokePage> {
  late Joke _currentJoke;
  final Random _random = Random();
  
  
  void initState() {
    super.initState();
    _getRandomJoke();
  }
  
  void _getRandomJoke() {
    setState(() {
      _currentJoke = widget.jokes[_random.nextInt(widget.jokes.length)];
    });
  }
}

随机页面设计

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [Colors.orange.shade300, Colors.orange.shade500],
    ),
  ),
  child: Column(
    children: [
      // 头部
      Text('随机笑话', style: TextStyle(color: Colors.white)),
      // 笑话卡片
      Card(
        child: Column(
          children: [
            Text(joke.category),
            Text(joke.title),
            Text(joke.content),
          ],
        ),
      ),
      // 换一个按钮
      ElevatedButton.icon(
        onPressed: _getRandomJoke,
        icon: Icon(Icons.refresh),
        label: Text('换一个'),
      ),
    ],
  ),
)

4. 收藏功能

使用列表管理收藏的笑话。

class _JokeHomePageState extends State<JokeHomePage> {
  final List<Joke> _favorites = [];
  
  void _toggleFavorite(Joke joke) {
    setState(() {
      joke.isFavorite = !joke.isFavorite;
      if (joke.isFavorite) {
        _favorites.add(joke);
      } else {
        _favorites.remove(joke);
      }
    });
  }
}

收藏页面空状态

Widget _buildEmptyState() {
  return Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(
          Icons.favorite_border,
          size: 80,
          color: Colors.grey.shade300,
        ),
        Text('还没有收藏的笑话'),
        Text('快去收藏你喜欢的笑话吧'),
      ],
    ),
  );
}

5. 复制分享功能

使用Clipboard API复制文本。

import 'package:flutter/services.dart';

void _copyJoke(Joke joke) {
  Clipboard.setData(ClipboardData(
    text: '${joke.title}\n\n${joke.content}',
  ));
  
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('已复制到剪贴板')),
  );
}

分享流程

  1. 用户点击复制按钮
  2. 将笑话内容复制到剪贴板
  3. 显示SnackBar提示
  4. 用户可以粘贴到其他应用分享

6. 笑话详情页面

class JokeDetailPage extends StatelessWidget {
  final Joke joke;
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('笑话详情'),
        actions: [
          IconButton(
            icon: Icon(joke.isFavorite ? Icons.favorite : Icons.favorite_border),
            onPressed: () => onToggleFavorite(joke),
          ),
          IconButton(
            icon: Icon(Icons.share),
            onPressed: () => _shareJoke(joke),
          ),
        ],
      ),
      body: SingleChildScrollView(
        child: Column(
          children: [
            // 渐变头部
            Container(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  colors: [Colors.orange.shade400, Colors.orange.shade600],
                ),
              ),
              child: Column(
                children: [
                  Text(joke.category),
                  Text(joke.title, style: TextStyle(color: Colors.white)),
                ],
              ),
            ),
            // 内容区域
            Padding(
              padding: EdgeInsets.all(24),
              child: Column(
                children: [
                  Text(joke.content, style: TextStyle(height: 1.8)),
                  Row(
                    children: [
                      Icon(Icons.thumb_up_outlined),
                      Text('${joke.likes} 人觉得好笑'),
                    ],
                  ),
                  // 操作按钮
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceAround,
                    children: [
                      _buildActionButton(Icons.thumb_up_outlined, '点赞'),
                      _buildActionButton(Icons.copy, '复制'),
                      _buildActionButton(Icons.share, '分享'),
                    ],
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

操作按钮设计

Widget _buildActionButton(IconData icon, String label, Color color) {
  return Column(
    children: [
      Container(
        width: 56,
        height: 56,
        decoration: BoxDecoration(
          color: color.withValues(alpha: 0.1),
          borderRadius: BorderRadius.circular(28),
        ),
        child: Icon(icon, color: color, size: 28),
      ),
      Text(label, style: TextStyle(fontSize: 12)),
    ],
  );
}

UI组件设计

1. 渐变头部

Container(
  padding: const EdgeInsets.fromLTRB(16, 48, 16, 16),
  decoration: BoxDecoration(
    gradient: LinearGradient(
      colors: [Colors.orange.shade400, Colors.orange.shade600],
    ),
  ),
  child: Row(
    children: [
      Icon(Icons.emoji_emotions, color: Colors.white, size: 32),
      Column(
        children: [
          Text('笑话大全', style: TextStyle(color: Colors.white)),
          Text('开心一刻,快乐每天', style: TextStyle(color: Colors.white70)),
        ],
      ),
      Container(
        decoration: BoxDecoration(
          color: Colors.white.withValues(alpha: 0.2),
          borderRadius: BorderRadius.circular(16),
        ),
        child: Text('${jokes.length}个笑话'),
      ),
    ],
  ),
)

2. 分类标签

Container(
  padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
  decoration: BoxDecoration(
    color: Colors.orange.shade100,
    borderRadius: BorderRadius.circular(12),
  ),
  child: Text(
    category,
    style: TextStyle(
      fontSize: 12,
      color: Colors.orange.shade700,
    ),
  ),
)

3. 卡片布局

Card(
  margin: EdgeInsets.only(bottom: 12),
  child: InkWell(
    onTap: () {},
    child: Padding(
      padding: EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 内容
        ],
      ),
    ),
  ),
)

4. NavigationBar底部导航

NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) {
    setState(() {
      _selectedIndex = index;
    });
  },
  destinations: const [
    NavigationDestination(
      icon: Icon(Icons.list_outlined),
      selectedIcon: Icon(Icons.list),
      label: '笑话',
    ),
    NavigationDestination(
      icon: Icon(Icons.shuffle_outlined),
      selectedIcon: Icon(Icons.shuffle),
      label: '随机',
    ),
    NavigationDestination(
      icon: Icon(Icons.favorite_outline),
      selectedIcon: Icon(Icons.favorite),
      label: '收藏',
    ),
  ],
)

功能扩展建议

1. 接入笑话API

使用网络API获取更多笑话:

import 'package:http/http.dart' as http;
import 'dart:convert';

class JokeService {
  static const String baseUrl = 'https://api.example.com/jokes';
  
  Future<List<Joke>> getJokes({String? category}) async {
    final url = category != null 
        ? '$baseUrl?category=$category' 
        : baseUrl;
    
    final response = await http.get(Uri.parse(url));
    
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body);
      return data.map((json) => Joke.fromJson(json)).toList();
    }
    throw Exception('获取笑话失败');
  }
  
  Future<Joke> getRandomJoke() async {
    final response = await http.get(Uri.parse('$baseUrl/random'));
    
    if (response.statusCode == 200) {
      return Joke.fromJson(json.decode(response.body));
    }
    throw Exception('获取随机笑话失败');
  }
}

2. 数据持久化

使用SharedPreferences保存收藏:

import 'package:shared_preferences/shared_preferences.dart';

class StorageService {
  static const String _favoritesKey = 'favorite_jokes';
  
  Future<void> saveFavorites(List<Joke> favorites) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = favorites.map((joke) => joke.toJson()).toList();
    await prefs.setString(_favoritesKey, json.encode(jsonList));
  }
  
  Future<List<Joke>> loadFavorites() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonStr = prefs.getString(_favoritesKey);
    if (jsonStr == null) return [];
    
    final List<dynamic> jsonList = json.decode(jsonStr);
    return jsonList.map((json) => Joke.fromJson(json)).toList();
  }
}

3. 搜索功能

添加笑话搜索:

class SearchPage extends StatefulWidget {
  final List<Joke> jokes;
  
  
  State<SearchPage> createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {
  String _searchQuery = '';
  
  List<Joke> get _searchResults {
    if (_searchQuery.isEmpty) return widget.jokes;
    
    return widget.jokes.where((joke) {
      return joke.title.contains(_searchQuery) ||
             joke.content.contains(_searchQuery);
    }).toList();
  }
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TextField(
          decoration: InputDecoration(
            hintText: '搜索笑话...',
            border: InputBorder.none,
          ),
          onChanged: (value) {
            setState(() {
              _searchQuery = value;
            });
          },
        ),
      ),
      body: ListView.builder(
        itemCount: _searchResults.length,
        itemBuilder: (context, index) {
          return JokeCard(joke: _searchResults[index]);
        },
      ),
    );
  }
}

4. 评论功能

添加用户评论:

class Comment {
  final String username;
  final String content;
  final DateTime time;
  
  Comment({
    required this.username,
    required this.content,
    required this.time,
  });
}

class CommentsSection extends StatelessWidget {
  final List<Comment> comments;
  
  
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('评论 (${comments.length})'),
        ...comments.map((comment) {
          return ListTile(
            leading: CircleAvatar(
              child: Text(comment.username[0]),
            ),
            title: Text(comment.username),
            subtitle: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(comment.content),
                Text(
                  _formatTime(comment.time),
                  style: TextStyle(fontSize: 12, color: Colors.grey),
                ),
              ],
            ),
          );
        }),
      ],
    );
  }
}

5. 分享到社交平台

使用share_plus包分享:

dependencies:
  share_plus: ^7.2.2
import 'package:share_plus/share_plus.dart';

Future<void> shareJoke(Joke joke) async {
  await Share.share(
    '${joke.title}\n\n${joke.content}\n\n来自笑话大全App',
    subject: joke.title,
  );
}

// 分享图片
Future<void> shareJokeAsImage(Joke joke) async {
  // 生成笑话图片
  final image = await _generateJokeImage(joke);
  
  await Share.shareXFiles(
    [XFile(image.path)],
    text: '${joke.title}\n\n来自笑话大全App',
  );
}

6. 每日推荐

每天推荐一个笑话:

class DailyJokeService {
  Future<Joke> getDailyJoke() async {
    final prefs = await SharedPreferences.getInstance();
    final today = DateTime.now().toString().substring(0, 10);
    final lastDate = prefs.getString('daily_joke_date');
    
    if (lastDate == today) {
      // 返回今天已推荐的笑话
      final jokeJson = prefs.getString('daily_joke');
      if (jokeJson != null) {
        return Joke.fromJson(json.decode(jokeJson));
      }
    }
    
    // 生成新的每日笑话
    final random = Random(DateTime.now().day);
    final joke = jokes[random.nextInt(jokes.length)];
    
    await prefs.setString('daily_joke_date', today);
    await prefs.setString('daily_joke', json.encode(joke.toJson()));
    
    return joke;
  }
}

7. 笑话投稿

允许用户投稿笑话:

class SubmitJokePage extends StatefulWidget {
  
  State<SubmitJokePage> createState() => _SubmitJokePageState();
}

class _SubmitJokePageState extends State<SubmitJokePage> {
  final _titleController = TextEditingController();
  final _contentController = TextEditingController();
  String _selectedCategory = '冷笑话';
  
  Future<void> _submitJoke() async {
    final joke = {
      'title': _titleController.text,
      'content': _contentController.text,
      'category': _selectedCategory,
    };
    
    final response = await http.post(
      Uri.parse('https://api.example.com/jokes/submit'),
      body: json.encode(joke),
      headers: {'Content-Type': 'application/json'},
    );
    
    if (response.statusCode == 200) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('投稿成功,审核通过后将展示')),
      );
      Navigator.pop(context);
    }
  }
  
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('投稿笑话')),
      body: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: _titleController,
              decoration: InputDecoration(labelText: '标题'),
            ),
            DropdownButton<String>(
              value: _selectedCategory,
              items: categories.map((cat) {
                return DropdownMenuItem(value: cat, child: Text(cat));
              }).toList(),
              onChanged: (value) {
                setState(() {
                  _selectedCategory = value!;
                });
              },
            ),
            TextField(
              controller: _contentController,
              decoration: InputDecoration(labelText: '内容'),
              maxLines: 10,
            ),
            ElevatedButton(
              onPressed: _submitJoke,
              child: Text('提交'),
            ),
          ],
        ),
      ),
    );
  }
}

8. 主题切换

支持多种主题:

class ThemeProvider extends ChangeNotifier {
  ThemeMode _themeMode = ThemeMode.light;
  
  ThemeMode get themeMode => _themeMode;
  
  void toggleTheme() {
    _themeMode = _themeMode == ThemeMode.light 
        ? ThemeMode.dark 
        : ThemeMode.light;
    notifyListeners();
  }
}

// 在MaterialApp中使用
MaterialApp(
  themeMode: themeProvider.themeMode,
  theme: ThemeData.light().copyWith(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
  ),
  darkTheme: ThemeData.dark().copyWith(
    colorScheme: ColorScheme.fromSeed(
      seedColor: Colors.orange,
      brightness: Brightness.dark,
    ),
  ),
)

性能优化建议

1. 列表优化

使用ListView.builder按需构建:

ListView.builder(
  itemCount: jokes.length,
  itemBuilder: (context, index) {
    return JokeCard(joke: jokes[index]);
  },
)

2. 图片缓存

如果添加图片功能,使用缓存:

import 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: joke.imageUrl,
  placeholder: (context, url) => CircularProgressIndicator(),
  errorWidget: (context, url, error) => Icon(Icons.error),
)

3. 状态管理优化

对于大型应用,使用Provider:

class JokeProvider extends ChangeNotifier {
  List<Joke> _jokes = [];
  List<Joke> _favorites = [];
  
  List<Joke> get jokes => _jokes;
  List<Joke> get favorites => _favorites;
  
  void toggleFavorite(Joke joke) {
    joke.isFavorite = !joke.isFavorite;
    if (joke.isFavorite) {
      _favorites.add(joke);
    } else {
      _favorites.remove(joke);
    }
    notifyListeners();
  }
}

4. 懒加载

分页加载笑话:

class _JokeListPageState extends State<JokeListPage> {
  final ScrollController _scrollController = ScrollController();
  int _currentPage = 1;
  bool _isLoading = false;
  
  
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }
  
  void _onScroll() {
    if (_scrollController.position.pixels ==
        _scrollController.position.maxScrollExtent) {
      _loadMore();
    }
  }
  
  Future<void> _loadMore() async {
    if (_isLoading) return;
    
    setState(() {
      _isLoading = true;
    });
    
    final newJokes = await JokeService().getJokes(page: _currentPage + 1);
    
    setState(() {
      _jokes.addAll(newJokes);
      _currentPage++;
      _isLoading = false;
    });
  }
}

测试建议

1. 单元测试

测试笑话过滤逻辑:

void main() {
  group('笑话过滤测试', () {
    final jokes = [
      Joke(id: 1, title: '笑话1', content: '内容1', category: '冷笑话'),
      Joke(id: 2, title: '笑话2', content: '内容2', category: '脑筋急转弯'),
      Joke(id: 3, title: '笑话3', content: '内容3', category: '冷笑话'),
    ];
    
    test('全部分类应返回所有笑话', () {
      final filtered = filterJokes(jokes, '全部');
      expect(filtered.length, 3);
    });
    
    test('冷笑话分类应返回2个笑话', () {
      final filtered = filterJokes(jokes, '冷笑话');
      expect(filtered.length, 2);
      expect(filtered.every((j) => j.category == '冷笑话'), true);
    });
    
    test('收藏功能测试', () {
      final joke = jokes[0];
      expect(joke.isFavorite, false);
      
      toggleFavorite(joke);
      expect(joke.isFavorite, true);
      
      toggleFavorite(joke);
      expect(joke.isFavorite, false);
    });
  });
}

2. Widget测试

测试UI组件:

void main() {
  testWidgets('笑话卡片显示测试', (WidgetTester tester) async {
    final joke = Joke(
      id: 1,
      title: '测试笑话',
      content: '这是测试内容',
      category: '冷笑话',
      likes: 100,
    );
    
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: JokeCard(joke: joke),
        ),
      ),
    );
    
    expect(find.text('测试笑话'), findsOneWidget);
    expect(find.text('冷笑话'), findsOneWidget);
    expect(find.text('100'), findsOneWidget);
  });
  
  testWidgets('收藏按钮测试', (WidgetTester tester) async {
    final joke = Joke(
      id: 1,
      title: '测试',
      content: '内容',
      category: '冷笑话',
    );
    
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: JokeCard(
            joke: joke,
            onToggleFavorite: (j) {
              j.isFavorite = !j.isFavorite;
            },
          ),
        ),
      ),
    );
    
    expect(find.byIcon(Icons.favorite_border), findsOneWidget);
    
    await tester.tap(find.byIcon(Icons.favorite_border));
    await tester.pump();
    
    expect(joke.isFavorite, true);
  });
}

3. 集成测试

测试完整流程:

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  testWidgets('完整笑话浏览流程', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());
    
    // 1. 验证初始显示
    expect(find.text('笑话大全'), findsOneWidget);
    expect(find.text('全部'), findsOneWidget);
    
    // 2. 切换分类
    await tester.tap(find.text('冷笑话'));
    await tester.pumpAndSettle();
    
    // 3. 点击笑话卡片
    await tester.tap(find.byType(Card).first);
    await tester.pumpAndSettle();
    expect(find.text('笑话详情'), findsOneWidget);
    
    // 4. 收藏笑话
    await tester.tap(find.byIcon(Icons.favorite_border));
    await tester.pumpAndSettle();
    
    // 5. 返回并切换到收藏页面
    await tester.pageBack();
    await tester.pumpAndSettle();
    await tester.tap(find.text('收藏'));
    await tester.pumpAndSettle();
    
    // 6. 验证收藏列表
    expect(find.byType(Card), findsWidgets);
    
    // 7. 切换到随机页面
    await tester.tap(find.text('随机'));
    await tester.pumpAndSettle();
    expect(find.text('随机笑话'), findsOneWidget);
    
    // 8. 点击换一个
    await tester.tap(find.text('换一个'));
    await tester.pumpAndSettle();
  });
}

部署发布

1. Android打包

# 生成签名密钥
keytool -genkey -v -keystore ~/joke-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias joke

# 配置android/key.properties
storePassword=your_password
keyPassword=your_password
keyAlias=joke
storeFile=/path/to/joke-key.jks

# 构建APK
flutter build apk --release

# 构建App Bundle
flutter build appbundle --release

2. iOS打包

# 安装依赖
cd ios && pod install

# 构建IPA
flutter build ipa --release

3. 应用配置

pubspec.yaml中配置:

name: joke_app
description: 笑话大全应用
version: 1.0.0+1

flutter:
  uses-material-design: true

AndroidManifest.xml中配置:

<application
    android:label="笑话大全"
    android:icon="@mipmap/ic_launcher">

项目总结

技术亮点

  1. 分类筛选:使用计算属性实现高效过滤
  2. 随机功能:Random类实现随机笑话
  3. 收藏管理:列表操作实现收藏功能
  4. 剪贴板操作:Clipboard API实现复制分享
  5. 渐变设计:LinearGradient提升视觉效果
  6. Material Design 3:使用最新的NavigationBar组件
  7. 卡片布局:Card和InkWell实现优雅交互

学习收获

通过本项目,你将掌握:

  • 列表过滤和筛选
  • 随机数生成
  • 状态管理基础
  • 剪贴板操作
  • 页面导航
  • 卡片布局设计
  • 渐变效果实现
  • 空状态处理

应用场景

本应用适合以下场景:

  • 休闲娱乐:随时随地看笑话放松心情
  • 社交分享:复制笑话分享给朋友
  • 收藏管理:保存喜欢的笑话
  • 学习案例:作为Flutter列表应用的学习项目

后续优化方向

  1. 网络数据:接入笑话API获取更多内容
  2. 数据持久化:保存收藏和浏览历史
  3. 搜索功能:支持关键词搜索笑话
  4. 评论互动:用户可以评论笑话
  5. 分享优化:支持分享到社交平台
  6. 每日推荐:每天推荐一个笑话
  7. 用户投稿:允许用户投稿笑话
  8. 主题切换:支持深色模式

代码规范

项目遵循以下规范:

  • 使用const构造函数优化性能
  • 组件拆分,职责单一
  • 命名规范:驼峰命名法
  • 注释清晰,代码可读性强
  • 数据和UI分离
  • 响应式布局设计

项目结构

lib/
├── main.dart                  # 主入口文件
├── models/                    # 数据模型(可扩展)
│   └── joke.dart
├── pages/                     # 页面组件(可扩展)
│   ├── joke_list_page.dart
│   ├── random_joke_page.dart
│   ├── favorites_page.dart
│   └── joke_detail_page.dart
├── widgets/                   # 自定义组件(可扩展)
│   └── joke_card.dart
└── services/                  # 业务逻辑(可扩展)
    ├── joke_service.dart
    └── storage_service.dart

相关资源

推荐API

  • 笑话大全API:https://api.apiopen.top/api/getJoke
  • 随机笑话API:https://v.api.aa1.cn/api/api-wenan-gaoxiao/index.php

推荐库

  • http:网络请求
  • share_plus:分享功能
  • cached_network_image:图片缓存
  • provider:状态管理

学习资源

  • Flutter官方文档:https://flutter.dev
  • Material Design 3:https://m3.material.io
  • Dart语言教程:https://dart.dev

本项目提供了完整的笑话应用功能,代码结构清晰,易于扩展。你可以在此基础上添加更多功能,打造一款功能丰富的娱乐应用。笑话内容可以根据需要替换或扩充,也可以接入真实的笑话API获取海量内容。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐