Flutter for OpenHarmony 微动漫App实战:名言语录实现
通过网盘分享的文件:flutter1.zip
链接: https://pan.baidu.com/s/1jkLZ9mZXjNm0LgP6FTVRzw 提取码: 2t97
动漫里有很多经典台词,让人印象深刻。微动漫App的名言语录功能,用卡片式的翻页效果展示这些名言,用户可以左右滑动浏览不同的名言。
这篇文章会实现名言语录页面,重点讲解 PageView 组件的使用、页面指示器的实现,以及如何设计一个有格调的名言卡片。

名言页面的设计思路
名言不同于普通的列表内容,它需要沉浸式的阅读体验。每次只展示一条名言,让用户专注于当前内容。
交互方式:左右滑动切换名言,比点击按钮更自然。
视觉设计:大字号、居中排版、引号装饰,营造阅读氛围。
信息展示:名言内容、角色名、动漫名,三层信息由主到次。
页面状态管理
名言页面需要管理几个状态:
import 'package:flutter/material.dart';
import '../services/api_service.dart';
import '../models/character.dart';
import '../widgets/shimmer_loading.dart';
class QuotesScreen extends StatefulWidget {
const QuotesScreen({super.key});
State<QuotesScreen> createState() => _QuotesScreenState();
}
class _QuotesScreenState extends State<QuotesScreen> {
List<AnimeQuote> _quotes = [];
bool _isLoading = true;
int _currentIndex = 0;
_quotes 存储名言列表,_isLoading 标记加载状态,_currentIndex 记录当前显示的是第几条名言。
用 StatefulWidget 是因为这些状态会随用户操作变化。
初始化加载数据
void initState() {
super.initState();
_loadQuotes();
}
Future<void> _loadQuotes() async {
setState(() => _isLoading = true);
try {
final quotes = await ApiService.getRandomQuotes(count: 20);
setState(() {
_quotes = quotes;
_isLoading = false;
});
} catch (e) {
setState(() => _isLoading = false);
}
}
initState 里调用 _loadQuotes 加载数据。一次加载 20 条名言,够用户滑动一会儿。
加载前设置 _isLoading = true 显示加载状态,加载完成后设置为 false。try-catch 捕获异常,即使加载失败也要更新状态。
页面主体结构
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('动漫名言')),
body: _isLoading
? const ShimmerLoading(itemCount: 1, isGrid: false)
: _quotes.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.format_quote, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
'暂无名言',
style: TextStyle(color: Colors.grey[600], fontSize: 16),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadQuotes,
child: const Text('重新加载'),
),
],
),
)
: PageView.builder(
onPageChanged: (index) => setState(() => _currentIndex = index),
itemCount: _quotes.length,
itemBuilder: (_, i) => _buildQuoteCard(_quotes[i]),
),
);
}
三种状态对应三种UI:加载中显示骨架屏,数据为空显示空状态,有数据显示 PageView。
三元表达式嵌套实现条件渲染,代码紧凑但可读性还行。如果逻辑更复杂,建议抽成单独的方法。
空状态设计
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.format_quote, size: 64, color: Colors.grey[400]),
const SizedBox(height: 16),
Text(
'暂无名言',
style: TextStyle(color: Colors.grey[600], fontSize: 16),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: _loadQuotes,
child: const Text('重新加载'),
),
],
),
)
空状态不能只显示一行文字,要有图标、说明、操作按钮。Icons.format_quote 是引号图标,和名言主题呼应。
ElevatedButton 让用户可以重新加载,不用退出页面再进来。
PageView 组件详解
PageView 是 Flutter 的翻页组件,每个子元素占满一页:
PageView.builder(
onPageChanged: (index) => setState(() => _currentIndex = index),
itemCount: _quotes.length,
itemBuilder: (_, i) => _buildQuoteCard(_quotes[i]),
)
PageView.builder 是懒加载版本,只构建当前可见的页面,性能更好。
onPageChanged 在页面切换时触发,更新 _currentIndex 用于页面指示器。
itemCount 是总页数,itemBuilder 构建每一页的内容。
PageView 的滚动方向
默认是水平滚动,可以改成垂直:
PageView.builder(
scrollDirection: Axis.vertical,
// 其他属性
)
对于名言这种内容,水平滑动更符合"翻页"的隐喻。垂直滑动更像"刷"内容,适合短视频类的场景。
名言卡片的实现
Widget _buildQuoteCard(AnimeQuote quote) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.format_quote,
size: 48,
color: Theme.of(context).primaryColor,
),
外层 Padding 设置 24 像素的边距,内容不会贴边。
Column 垂直排列内容,mainAxisAlignment.center 让内容垂直居中。
顶部的引号图标用主题色,作为视觉装饰。
名言文本样式
const SizedBox(height: 24),
Text(
quote.quote,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
height: 1.8,
),
),
名言文本是卡片的核心,字号设为 20,比正常文本大。fontWeight.w600 半粗体,强调但不过分。
height: 1.8 设置行高为字号的 1.8 倍,行与行之间有足够的呼吸空间。
textAlign: TextAlign.center 居中对齐,配合整体的居中布局。
角色和动漫信息
const SizedBox(height: 32),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Column(
children: [
Text(
quote.character,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
quote.anime,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
角色和动漫信息放在一个圆角容器里,背景用主题色的 10% 透明度,若隐若现。
BorderRadius.circular(20) 设置圆角,让容器看起来像一个标签。
角色名用粗体,动漫名用灰色小字,形成主次关系。
页面指示器
const SizedBox(height: 32),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_quotes.length,
(i) => Container(
width: 8,
height: 8,
margin: const EdgeInsets.symmetric(horizontal: 4),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: i == _currentIndex
? Theme.of(context).primaryColor
: Colors.grey[300],
),
),
),
),
页面指示器是一排小圆点,当前页对应的圆点用主题色高亮。
List.generate 根据名言数量生成圆点列表。每个圆点是 8x8 的圆形容器,左右各 4 像素间距。
i == _currentIndex 判断是否是当前页,决定用什么颜色。
指示器的优化
当名言数量很多时,一排圆点会很长。可以只显示当前页附近的几个:
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_currentIndex > 0)
const Icon(Icons.chevron_left, color: Colors.grey),
Text(
'${_currentIndex + 1} / ${_quotes.length}',
style: const TextStyle(color: Colors.grey),
),
if (_currentIndex < _quotes.length - 1)
const Icon(Icons.chevron_right, color: Colors.grey),
],
)
改成数字形式 “3 / 20”,左右箭头提示还有更多内容。这种方式不管有多少页都能显示。
添加刷新功能
用户看完所有名言后,可能想看新的:
Scaffold(
appBar: AppBar(
title: const Text('动漫名言'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadQuotes,
),
],
),
// body
)
在 AppBar 右侧加个刷新按钮,点击后重新加载名言。
添加分享功能
名言很适合分享:
Widget _buildQuoteCard(AnimeQuote quote) {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 名言内容
const SizedBox(height: 24),
IconButton(
icon: const Icon(Icons.share),
onPressed: () {
final text = '"${quote.quote}"\n\n—— ${quote.character}《${quote.anime}》';
// 调用分享功能
},
),
],
),
);
}
分享文本格式化成:引号包裹的名言 + 角色名 + 动漫名。这种格式在社交媒体上看起来很专业。
添加收藏功能
用户可能想收藏喜欢的名言:
class _QuotesScreenState extends State<QuotesScreen> {
List<AnimeQuote> _quotes = [];
Set<String> _favoriteQuotes = {};
void _toggleFavorite(AnimeQuote quote) {
setState(() {
if (_favoriteQuotes.contains(quote.quote)) {
_favoriteQuotes.remove(quote.quote);
} else {
_favoriteQuotes.add(quote.quote);
}
});
}
用 Set 存储收藏的名言,用名言内容作为唯一标识。
IconButton(
icon: Icon(
_favoriteQuotes.contains(quote.quote)
? Icons.favorite
: Icons.favorite_border,
color: _favoriteQuotes.contains(quote.quote)
? Colors.red
: null,
),
onPressed: () => _toggleFavorite(quote),
)
收藏按钮根据状态显示实心或空心爱心,收藏后变红色。
自动播放功能
可以加个自动播放,像幻灯片一样:
class _QuotesScreenState extends State<QuotesScreen> {
final PageController _pageController = PageController();
Timer? _autoPlayTimer;
bool _isAutoPlaying = false;
void _startAutoPlay() {
_autoPlayTimer = Timer.periodic(
const Duration(seconds: 5),
(_) {
if (_currentIndex < _quotes.length - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
} else {
_pageController.animateToPage(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
},
);
setState(() => _isAutoPlaying = true);
}
void _stopAutoPlay() {
_autoPlayTimer?.cancel();
setState(() => _isAutoPlaying = false);
}
Timer.periodic 每 5 秒触发一次,调用 _pageController.nextPage 切换到下一页。到最后一页时跳回第一页。
PageController 可以编程控制 PageView 的滚动。
动画效果增强
给名言文本加个淡入动画:
class _QuotesScreenState extends State<QuotesScreen>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _fadeAnimation;
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_fadeAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(parent: _animationController, curve: Curves.easeIn),
);
_loadQuotes();
}
数据加载完成后启动动画:
setState(() {
_quotes = quotes;
_isLoading = false;
});
_animationController.forward();
在卡片中使用动画:
FadeTransition(
opacity: _fadeAnimation,
child: Text(quote.quote, ...),
)
FadeTransition 根据动画值控制透明度,实现淡入效果。
手势增强
除了滑动,还可以加双击收藏:
GestureDetector(
onDoubleTap: () => _toggleFavorite(quote),
child: _buildQuoteCard(quote),
)
GestureDetector 包裹卡片,监听双击事件。这是很多App常用的交互方式。
深色模式适配
名言卡片在深色模式下需要调整:
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.dark
? Theme.of(context).primaryColor.withOpacity(0.2)
: Theme.of(context).primaryColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
// 内容
)
深色模式下背景色透明度稍高一点,让标签更明显。
小结
名言语录页面涉及的技术点:PageView 翻页组件、PageController 编程控制、页面指示器实现、Timer 定时器、GestureDetector 手势识别、FadeTransition 淡入动画。
设计上,名言需要沉浸式的阅读体验,一次只展示一条,大字号居中排版,引号装饰增加格调。
交互上,滑动切换是主要方式,还可以加刷新、分享、收藏、自动播放等功能,让页面更有趣。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)