Flutter实战:打造随机名言/毒鸡汤应用

前言

名言警句能给人启发和鼓励,而毒鸡汤则以幽默的方式调侃生活。本文将带你从零开始,使用Flutter开发一个功能完整的随机名言应用,支持多种类型、收藏、分享等功能。

应用特色

  • 📚 六种名言类型:励志名言、毒鸡汤、哲理名言、搞笑段子、爱情语录、人生感悟
  • 🎲 随机生成:点击按钮随机显示名言
  • 🏷️ 类型筛选:按类型筛选名言
  • ❤️ 收藏功能:收藏喜欢的名言
  • 📋 复制功能:一键复制到剪贴板
  • 📤 分享功能:分享到社交平台
  • 淡入动画:切换名言时的平滑过渡
  • 💾 数据持久化:收藏数据本地保存
  • 🎨 渐变设计:每种类型都有独特配色
  • 🌓 深色模式:自动适配系统主题

效果展示

在这里插入图片描述
在这里插入图片描述

随机名言

名言类型

励志名言

成功励志

坚持不懈

毒鸡汤

反鸡汤

现实讽刺

哲理名言

哲学思考

人生智慧

搞笑段子

幽默风趣

自嘲调侃

爱情语录

浪漫情话

爱情感悟

人生感悟

生活哲理

人生体悟

核心功能

随机生成

类型筛选

收藏管理

复制分享

淡入动画

数据模型设计

1. 名言类型枚举

enum QuoteType {
  motivational,  // 励志名言
  poisonous,     // 毒鸡汤
  philosophical, // 哲理名言
  funny,         // 搞笑段子
  love,          // 爱情语录
  life;          // 人生感悟

  String get label {
    switch (this) {
      case QuoteType.motivational: return '励志名言';
      case QuoteType.poisonous: return '毒鸡汤';
      // ...
    }
  }

  IconData get icon {
    switch (this) {
      case QuoteType.motivational: return Icons.trending_up;
      case QuoteType.poisonous: return Icons.local_drink;
      // ...
    }
  }

  Color get color {
    switch (this) {
      case QuoteType.motivational: return Colors.orange;
      case QuoteType.poisonous: return Colors.green;
      // ...
    }
  }
}

2. 名言模型

class Quote {
  final String text;          // 名言文本
  final String? author;       // 作者(可选)
  final QuoteType type;       // 类型
  bool isFavorite;            // 是否收藏

  Quote({
    required this.text,
    this.author,
    required this.type,
    this.isFavorite = false,
  });

  // JSON序列化
  Map<String, dynamic> toJson() => {
    'text': text,
    'author': author,
    'type': type.index,
    'isFavorite': isFavorite,
  };

  // JSON反序列化
  factory Quote.fromJson(Map<String, dynamic> json) => Quote(
    text: json['text'],
    author: json['author'],
    type: QuoteType.values[json['type']],
    isFavorite: json['isFavorite'] ?? false,
  );
}

名言数据库

1. 励志名言

static final List<Map<String, String>> motivationalQuotes = [
  {
    'text': '成功不是终点,失败也不是终结,唯有勇气才是永恒。',
    'author': '温斯顿·丘吉尔'
  },
  {
    'text': '你的时间有限,不要浪费在重复他人的生活上。',
    'author': '史蒂夫·乔布斯'
  },
  {
    'text': '世界上只有一种真正的英雄主义,那就是认清生活的真相后依然热爱它。',
    'author': '罗曼·罗兰'
  },
  // ... 更多名言
];

2. 毒鸡汤

static final List<Map<String, String>> poisonousQuotes = [
  {'text': '你只是看起来很努力,实际上只是效率低下。', 'author': ''},
  {'text': '比你优秀的人还在努力,你有什么资格不努力?但你努力了也没用。', 'author': ''},
  {'text': '失败并不可怕,可怕的是你还相信这句话。', 'author': ''},
  {'text': '你以为有钱人很快乐吗?他们的快乐你根本想象不到。', 'author': ''},
  {'text': '条条大路通罗马,而有些人就住在罗马。', 'author': ''},
  // ... 更多毒鸡汤
];

3. 随机获取名言

static Quote getRandomQuote([QuoteType? type]) {
  final random = Random();
  if (type != null) {
    // 获取指定类型的名言
    final quotes = getQuotesByType(type);
    return quotes[random.nextInt(quotes.length)];
  } else {
    // 随机选择类型
    final allTypes = QuoteType.values;
    final randomType = allTypes[random.nextInt(allTypes.length)];
    return getRandomQuote(randomType);
  }
}

核心功能实现

1. 淡入动画

class _QuotePageState extends State<QuotePage> 
    with TickerProviderStateMixin {
  late AnimationController _fadeController;
  late Animation<double> _fadeAnimation;

  
  void initState() {
    super.initState();
    _fadeController = AnimationController(
      duration: const Duration(milliseconds: 500),
      vsync: this,
    );
    
    _fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _fadeController, curve: Curves.easeIn),
    );
  }

  void _generateNewQuote() {
    setState(() {
      _currentQuote = QuoteDatabase.getRandomQuote(_selectedType);
    });

    // 重置并播放动画
    _fadeController.reset();
    _fadeController.forward();
  }
}

2. 名言卡片

Widget _buildQuoteCard() {
  return FadeTransition(
    opacity: _fadeAnimation,
    child: Container(
      margin: const EdgeInsets.symmetric(horizontal: 16),
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
          colors: [
            _currentQuote!.type.color.withOpacity(0.1),
            _currentQuote!.type.color.withOpacity(0.05),
          ],
        ),
        borderRadius: BorderRadius.circular(20),
        border: Border.all(
          color: _currentQuote!.type.color.withOpacity(0.3),
          width: 2,
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 类型标签
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
            decoration: BoxDecoration(
              color: _currentQuote!.type.color.withOpacity(0.2),
              borderRadius: BorderRadius.circular(16),
            ),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                Icon(_currentQuote!.type.icon, size: 16),
                const SizedBox(width: 6),
                Text(_currentQuote!.type.label),
              ],
            ),
          ),
          const SizedBox(height: 20),
          // 引号图标
          Icon(
            Icons.format_quote,
            size: 32,
            color: _currentQuote!.type.color.withOpacity(0.5),
          ),
          const SizedBox(height: 12),
          // 名言文本
          Text(
            _currentQuote!.text,
            style: const TextStyle(
              fontSize: 20,
              height: 1.6,
              fontWeight: FontWeight.w500,
            ),
          ),
          const SizedBox(height: 16),
          // 作者
          if (_currentQuote!.author != null)
            Align(
              alignment: Alignment.centerRight,
              child: Text(
                '—— ${_currentQuote!.author}',
                style: TextStyle(
                  fontSize: 14,
                  fontStyle: FontStyle.italic,
                  color: Colors.grey.shade600,
                ),
              ),
            ),
        ],
      ),
    ),
  );
}

3. 收藏功能

void _toggleFavorite() {
  if (_currentQuote == null) return;

  setState(() {
    _currentQuote!.isFavorite = !_currentQuote!.isFavorite;

    if (_currentQuote!.isFavorite) {
      _favorites.add(_currentQuote!);
    } else {
      _favorites.removeWhere((q) => q.text == _currentQuote!.text);
    }
  });

  _saveFavorites();

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(
        _currentQuote!.isFavorite ? '已添加到收藏' : '已取消收藏'
      ),
      duration: const Duration(seconds: 1),
    ),
  );
}

Future<void> _saveFavorites() async {
  final prefs = await SharedPreferences.getInstance();
  final String encoded = json.encode(
    _favorites.map((q) => q.toJson()).toList()
  );
  await prefs.setString('favorites', encoded);
}

4. 复制到剪贴板

void _copyToClipboard() {
  if (_currentQuote == null) return;

  final text = _currentQuote!.author != null
      ? '${_currentQuote!.text}\n\n—— ${_currentQuote!.author}'
      : _currentQuote!.text;

  Clipboard.setData(ClipboardData(text: text));

  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('已复制到剪贴板'),
      duration: Duration(seconds: 1),
    ),
  );
}

5. 分享功能

void _shareQuote() {
  if (_currentQuote == null) return;

  final text = _currentQuote!.author != null
      ? '${_currentQuote!.text}\n\n—— ${_currentQuote!.author}'
      : _currentQuote!.text;

  Share.share(text);
}

6. 类型筛选

Widget _buildTypeFilter() {
  return SizedBox(
    height: 50,
    child: ListView(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 16),
      children: [
        // 全部选项
        FilterChip(
          label: const Text('全部'),
          selected: _selectedType == null,
          onSelected: (selected) {
            setState(() => _selectedType = null);
            _generateNewQuote();
          },
        ),
        // 各类型选项
        ...QuoteType.values.map((type) {
          return FilterChip(
            avatar: Icon(type.icon, size: 18),
            label: Text(type.label),
            selected: _selectedType == type,
            onSelected: (selected) {
              setState(() {
                _selectedType = selected ? type : null;
              });
              _generateNewQuote();
            },
          );
        }),
      ],
    ),
  );
}

UI组件设计

1. 操作按钮

Widget _buildActionButtons() {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      _buildActionButton(
        icon: _currentQuote?.isFavorite == true
            ? Icons.favorite
            : Icons.favorite_border,
        label: '收藏',
        color: Colors.red,
        onPressed: _toggleFavorite,
      ),
      _buildActionButton(
        icon: Icons.copy,
        label: '复制',
        color: Colors.blue,
        onPressed: _copyToClipboard,
      ),
      _buildActionButton(
        icon: Icons.share,
        label: '分享',
        color: Colors.green,
        onPressed: _shareQuote,
      ),
    ],
  );
}

Widget _buildActionButton({
  required IconData icon,
  required String label,
  required Color color,
  required VoidCallback onPressed,
}) {
  return Column(
    children: [
      IconButton.filled(
        onPressed: onPressed,
        icon: Icon(icon),
        style: IconButton.styleFrom(
          backgroundColor: color.withOpacity(0.2),
          foregroundColor: color,
          padding: const EdgeInsets.all(16),
        ),
      ),
      const SizedBox(height: 8),
      Text(
        label,
        style: TextStyle(
          fontSize: 12,
          color: color,
          fontWeight: FontWeight.w500,
        ),
      ),
    ],
  );
}

2. 收藏夹页面

class FavoritesPage extends StatelessWidget {
  final List<Quote> favorites;
  final Function(Quote) onDelete;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('收藏夹')),
      body: favorites.isEmpty
          ? _buildEmptyState()
          : ListView.builder(
              itemCount: favorites.length,
              itemBuilder: (context, index) {
                return _buildFavoriteItem(context, favorites[index]);
              },
            ),
    );
  }

  Widget _buildFavoriteItem(BuildContext context, Quote quote) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                // 类型标签
                Container(
                  padding: const EdgeInsets.symmetric(
                    horizontal: 10,
                    vertical: 4,
                  ),
                  decoration: BoxDecoration(
                    color: quote.type.color.withOpacity(0.2),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Row(
                    children: [
                      Icon(quote.type.icon, size: 14),
                      const SizedBox(width: 4),
                      Text(quote.type.label),
                    ],
                  ),
                ),
                const Spacer(),
                // 删除按钮
                IconButton(
                  icon: const Icon(Icons.delete_outline),
                  onPressed: () => _showDeleteDialog(context, quote),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Text(quote.text),
            if (quote.author != null)
              Text('—— ${quote.author}'),
          ],
        ),
      ),
    );
  }
}

3. Badge徽章

显示收藏数量:

IconButton(
  icon: Badge(
    label: Text(_favorites.length.toString()),
    isLabelVisible: _favorites.isNotEmpty,
    child: const Icon(Icons.favorite),
  ),
  onPressed: () {
    // 打开收藏夹
  },
)

技术要点详解

1. FadeTransition动画

属性 说明
opacity 透明度动画
child 子组件
duration 动画时长
curve 动画曲线
FadeTransition(
  opacity: _fadeAnimation,
  child: Widget(),
)

2. Clipboard剪贴板操作

// 复制到剪贴板
Clipboard.setData(ClipboardData(text: 'Hello'));

// 从剪贴板读取
ClipboardData? data = await Clipboard.getData('text/plain');
String? text = data?.text;

3. Share分享功能

// 分享文本
Share.share('分享内容');

// 分享文件
Share.shareXFiles([XFile('path/to/file')]);

// 分享带标题
Share.share('内容', subject: '标题');

4. FilterChip筛选器

FilterChip(
  label: Text('标签'),
  avatar: Icon(Icons.star),      // 前置图标
  selected: isSelected,          // 是否选中
  onSelected: (bool selected) {  // 选中回调
    // 处理选中状态
  },
)

功能扩展建议

1. 每日一句

每天推送一条名言:

class DailyQuote {
  static Future<Quote> getTodayQuote() async {
    final prefs = await SharedPreferences.getInstance();
    final today = DateTime.now().toIso8601String().split('T')[0];
    final savedDate = prefs.getString('daily_quote_date');
    
    if (savedDate != today) {
      // 生成新的每日名言
      final quote = QuoteDatabase.getRandomQuote();
      await prefs.setString('daily_quote_date', today);
      await prefs.setString('daily_quote', json.encode(quote.toJson()));
      return quote;
    } else {
      // 返回今天的名言
      final quoteJson = prefs.getString('daily_quote');
      return Quote.fromJson(json.decode(quoteJson!));
    }
  }
}

2. 本地通知

定时推送名言:

import 'package:flutter_local_notifications/flutter_local_notifications.dart';

class NotificationService {
  static final FlutterLocalNotificationsPlugin _notifications =
      FlutterLocalNotificationsPlugin();

  static Future<void> scheduleDailyQuote() async {
    final quote = QuoteDatabase.getRandomQuote();
    
    await _notifications.zonedSchedule(
      0,
      '每日名言',
      quote.text,
      _nextInstanceOf9AM(),
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'daily_quote',
          '每日名言',
          importance: Importance.high,
        ),
      ),
      uiLocalNotificationDateInterpretation:
          UILocalNotificationDateInterpretation.absoluteTime,
      matchDateTimeComponents: DateTimeComponents.time,
    );
  }
}

3. 主题背景

为不同类型设置不同背景:

class QuoteBackground extends StatelessWidget {
  final QuoteType type;
  final Widget child;

  
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        image: DecorationImage(
          image: AssetImage('assets/bg_${type.name}.jpg'),
          fit: BoxFit.cover,
          opacity: 0.3,
        ),
      ),
      child: child,
    );
  }
}

4. 语音朗读

使用TTS朗读名言:

import 'package:flutter_tts/flutter_tts.dart';

class TTSService {
  static final FlutterTts _tts = FlutterTts();

  static Future<void> speak(String text) async {
    await _tts.setLanguage('zh-CN');
    await _tts.setPitch(1.0);
    await _tts.setSpeechRate(0.5);
    await _tts.speak(text);
  }

  static Future<void> stop() async {
    await _tts.stop();
  }
}

5. 图片生成

将名言生成为图片:

import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';

Future<Uint8List> generateQuoteImage(Quote quote) async {
  final recorder = ui.PictureRecorder();
  final canvas = Canvas(recorder);
  final size = const Size(800, 600);

  // 绘制背景
  final paint = Paint()..color = quote.type.color.withOpacity(0.1);
  canvas.drawRect(Offset.zero & size, paint);

  // 绘制文本
  final textPainter = TextPainter(
    text: TextSpan(
      text: quote.text,
      style: const TextStyle(fontSize: 24, color: Colors.black),
    ),
    textDirection: TextDirection.ltr,
  );
  textPainter.layout(maxWidth: size.width - 40);
  textPainter.paint(canvas, const Offset(20, 100));

  // 转换为图片
  final picture = recorder.endRecording();
  final image = await picture.toImage(
    size.width.toInt(),
    size.height.toInt(),
  );
  final byteData = await image.toByteData(
    format: ui.ImageByteFormat.png,
  );
  return byteData!.buffer.asUint8List();
}

6. 搜索功能

List<Quote> searchQuotes(String query) {
  final allQuotes = <Quote>[];
  for (var type in QuoteType.values) {
    allQuotes.addAll(QuoteDatabase.getQuotesByType(type));
  }

  return allQuotes.where((quote) {
    return quote.text.toLowerCase().contains(query.toLowerCase()) ||
        (quote.author?.toLowerCase().contains(query.toLowerCase()) ?? false);
  }).toList();
}

性能优化建议

1. 名言预加载

class QuoteCache {
  static final Map<QuoteType, List<Quote>> _cache = {};

  static void preload() {
    for (var type in QuoteType.values) {
      _cache[type] = QuoteDatabase.getQuotesByType(type);
    }
  }

  static Quote getRandomQuote(QuoteType type) {
    final quotes = _cache[type]!;
    return quotes[Random().nextInt(quotes.length)];
  }
}

2. 收藏夹分页

class _FavoritesPageState extends State<FavoritesPage> {
  int _displayCount = 20;
  final ScrollController _scrollController = ScrollController();

  
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }

  void _onScroll() {
    if (_scrollController.position.pixels ==
        _scrollController.position.maxScrollExtent) {
      setState(() {
        _displayCount += 20;
      });
    }
  }

  List<Quote> get _displayedFavorites {
    return widget.favorites.take(_displayCount).toList();
  }
}

常见问题解答

Q1: 如何添加更多名言?

A: 在QuoteDatabase类中添加新的名言数据:

static final List<Map<String, String>> motivationalQuotes = [
  // 现有名言
  {'text': '新的名言', 'author': '作者'},
];

Q2: 如何自定义名言类型?

A: 在QuoteType枚举中添加新类型:

enum QuoteType {
  motivational,
  poisonous,
  custom;  // 新类型

  String get label {
    switch (this) {
      case QuoteType.custom: return '自定义';
      // ...
    }
  }
}

Q3: 如何实现名言历史记录?

A: 添加历史记录功能:

class QuoteHistory {
  static final List<Quote> _history = [];

  static void add(Quote quote) {
    _history.insert(0, quote);
    if (_history.length > 100) {
      _history.removeLast();
    }
  }

  static List<Quote> getHistory() => _history;
}

项目结构

lib/
├── main.dart                    # 主程序入口
├── models/
│   ├── quote_type.dart         # 名言类型枚举
│   └── quote.dart              # 名言模型
├── data/
│   └── quote_database.dart     # 名言数据库
├── screens/
│   ├── quote_page.dart         # 主页面
│   └── favorites_page.dart     # 收藏夹页面
├── widgets/
│   ├── quote_card.dart         # 名言卡片
│   ├── type_filter.dart        # 类型筛选器
│   └── action_buttons.dart     # 操作按钮
└── services/
    ├── storage_service.dart    # 存储服务
    └── share_service.dart      # 分享服务

总结

本文实现了一个功能完整的随机名言应用,涵盖了以下核心技术:

  1. 动画设计:FadeTransition淡入效果
  2. 枚举扩展:为枚举添加属性和方法
  3. 数据持久化:SharedPreferences + JSON序列化
  4. 剪贴板操作:Clipboard API
  5. 分享功能:share_plus包
  6. Material 3设计:FilterChip、Badge等组件

通过本项目,你不仅学会了如何实现名言应用,还掌握了Flutter中动画、数据管理、系统交互的核心技术。这些知识可以应用到更多场景,如每日一句、心灵鸡汤、语录分享等领域。

名言虽短,却能给人启发。希望这个应用能为用户带来正能量和思考!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐