听诗吧 是一个集 诗词/诗人推荐、搜索、简介、赏析、朗读(下个版本) 于一体的诗词兴趣 APP。涵盖了从古代到近代的 20,000 位诗人的 500,000 首诗词作品,叙事诗、抒情诗、送别诗、边塞诗、山水田园诗、怀古诗、悼亡诗,咏物诗 应有尽有😁。
- APP 端基于 Google 最新研发的 Flutter UI 框架,一套代码库高效构建多平台精美应用(移动、Web、桌面和嵌入式平台)😊,配合 MaterialDesign 的设计风格 和 卡片式布局 让你眼前一亮。更有微信分享功能,好东西当然要分享👍~
项目地址: https://github.com/mmtou/listen_poetry_app
- API 端基于 SpringBoot 微服务架构 和 轻量级的 MySQL 数据库,给你带来高效、稳定的服务体验。更集成了百度的语音合成技术,让你畅快的享受诗词带来的乐趣😍。
https://github.com/mmtou/listenpoetry-api
├── components // 组件
│ ├── avatar.dart // 头像组件,前期根据名称生成对应颜色的头像
│ ├── empty.dart // 置空组件
│ ├── poet.dart // 诗人列表
│ ├── poet_item.dart // 单个诗人
│ ├── poetry.dart // 诗词列表
│ ├── poetry_item.dart // 单个诗词
│ └── recommend.dart // 推荐页组件
├── main.dart
├── utils // 工具类
│ ├── common.dart // 通用工具
│ ├── constant.dart // 常量
│ ├── favorite.dart // 点爱心的管理
│ ├── http.dart // 网络请求
└── views // 页面
├── feedback.dart // 反馈
├── index.dart // 首页
├── poet_detail.dart // 诗人
├── poetry_detail.dart // 诗词
└── search.dart // 搜索
- APP 启动时从本地读取点赞列表
void main() {
if (Platform.isAndroid) {
SystemUiOverlayStyle systemUiOverlayStyle =
SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
}
WidgetsFlutterBinding.ensureInitialized();
run();
}
void run() async {
await Favorite.getInstance();
runApp(MyApp());
}
...
class Favorite {
...
static Future<bool> getInstance() async {
prefs = await SharedPreferences.getInstance();
favorites = prefs.getStringList('favorites') ?? [];
return true;
}
...
}
- 头像组件,根据诗人名称,计算16位UTF-16代码单元,然后根据规定的颜色数组长度取余,得到index,从颜色数组的中得到某个色值,即为头像的背景色。
Widget build(BuildContext context) {
String name = Common.getShortName(authorName);
int index = name.codeUnitAt(0) % Constant.avatarColors.length;
Color color = Constant.avatarColors[index];
return InkWell(
onTap: () {
if (authorId != null) {
Common.toPoetDetail(context, authorId);
}
},
child: CircleAvatar(
backgroundColor: color,
child: Center(
child: Text(
name,
style: TextStyle(
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
);
}
- 微信分享,集成 fluwx 第三方插件,快速实现微信分享、登录、支付等功能。
- 创建 keystore,修改相应配置
- 查看签名,执行
keytool -list -v -keystore key.keystore
命令后的MD5去掉:转为小写即为微信开放平台的签名。 - 登录微信开放平台创建应用,填写包名、签名等信息
fluwx.registerWxApi( appId: "xxxx", universalLink: "xxxx"); fluwx.shareToWeChat(WeChatShareTextModel( text: content, transaction: "transaction}", scene: WeChatScene.SESSION));
http.dart
单例模式,减少资源浪费
import 'package:bot_toast/bot_toast.dart';
import 'package:dio/dio.dart';
import './constant.dart';
class Http {
Dio _dio;
// 工厂模式
factory Http() => _getInstance();
static Http get instance => _getInstance();
static Http _instance;
Http._internal() {
// 初始化
if (_dio == null) {
_dio = Dio(
BaseOptions(
baseUrl: Constant.host,
connectTimeout: 10000,
receiveTimeout: 6000,
headers: {'Content-Type': 'application/json'}),
);
_dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) async {
BotToast.showLoading();
return options; //continue
}, onResponse: (Response response) async {
BotToast.closeAllLoading();
var data = response.data;
if (!data['success']) {
BotToast.showText(text: data['message']);
throw (data['message']);
}
return response.data['result']; // continue
}, onError: (DioError e) async {
BotToast.closeAllLoading();
if ('DioErrorType.DEFAULT' == e.type.toString()) {
BotToast.showText(text: e.error);
} else {
BotToast.showText(text: '服务器异常');
}
// Do something with response error
return e; //continue
}));
}
}
static Http _getInstance() {
if (_instance == null) {
_instance = Http._internal();
}
return _instance;
}
Future get(uri, {queryParameters}) async {
try {
Response response = await _dio.get(uri, queryParameters: queryParameters);
print(response);
return response.data;
} catch (e) {
print(e);
}
}
Future post(uri, {json}) async {
try {
Response response = await _dio.post(uri, data: json);
return response.data;
} catch (e) {
print(e);
}
}
}
- 使用
TabView
时,避免页面重绘,造成资源浪费和体验下降;子组件集成AutomaticKeepAliveClientMixin
实现缓存策略,且在build
中加入super.build(context);
class Poetry extends StatefulWidget {
@override
_PoetryState createState() => _PoetryState();
}
class _PoetryState extends State<Poetry> with AutomaticKeepAliveClientMixin {
List list;
int pageNum = 1;
@override
Widget build(BuildContext context) {
super.build(context);
return EasyRefresh(
header:
BezierCircleHeader(backgroundColor: Theme.of(context).primaryColor),
footer:
BezierBounceFooter(backgroundColor: Theme.of(context).primaryColor),
onRefresh: () => refresh(),
onLoad: () => onLoad(),
child: this.list == null
? Empty('暂无数据~')
: ListView.separated(
padding: EdgeInsets.all(16),
itemCount: this.list.length,
separatorBuilder: (BuildContext context, int index) => Container(
height: 12,
),
itemBuilder: (BuildContext context, int index) {
var item = this.list[index];
return PoetryItem(item);
}),
);
}
refresh() async {
this.pageNum = 1;
this.list = [];
await this.getList();
return true;
}
onLoad() async {
pageNum += 1;
await this.getList();
return true;
}
// 诗词列表
getList() async {
var data = await Http().get('/poetry/list?pageNum=$pageNum');
if (data != null) {
List list = data['list'];
this.list = this.list ?? [];
setState(() {
this.list.addAll(list);
});
}
}
@override
void initState() {
super.initState();
this.getList();
}
@override
bool get wantKeepAlive => true;
InkWell
组件可以实现点击后的波纹反馈效果,但是InkWell
的child不能设置背景色,不然会覆盖掉波纹的效果。需要你又需要背景色怎么办呢?可以在InkWell
上包一层Ink
。
Ink(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: InkWell(
onTap: () => Common.toPoetryDetail(context, widget.poetry),
child: Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
),
),
)
);
- 推荐
- 广场
- 诗人
- 诗词详情
- 诗人详情
- 微信分享
- 搜索
- 诗词朗读
- 登录
未完待续~
愿你走出半生,归来仍是少年。