Flutter:扩展上周的“书架” App,利用数据库来存储笔记和收藏!
Posted 承香墨影
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter:扩展上周的“书架” App,利用数据库来存储笔记和收藏!相关的知识,希望对你有一定的参考价值。
上周,分享了一遍 ,从零搭建一个简单的 App。今天继续分享它的续集,在原有 App 的基础之上,扩展出更多的功能,Flutter 让一切都变得简单,希望你能喜欢!
— 承香墨影
授权 承香墨影 翻译并发布
在上一篇关于 Flutter 的文章《》中,我谈到了我是如何在一个小时内,利用 Flutter 构建 App 的,该 App 已经完成。它的目的是通过列表展示网络上的图书,这些功能现在已经实现了,但是它们可以更好。
我想把本文写成一个系列,在整个系列中,我将继续改进应用程序并添加令人兴奋的新功能,并最终将其发布到 Google Play 商店和 Apple 的 App Store 上。
在这篇文字中,我们将介绍如何使用数据库来存储笔记和喜欢的书籍。
我添加了几个类,如果你想了解细节,建议直接在 Github 上阅读源码( https://github.com/Norbert515/BookSearch)。
utils.dart 包含渐变的过渡动画,以及元素的转场动画。
还有一些其他的类,之后会解释。
我还将 BookCard 放入了一个小部件中(main.dart 内)。
该数据库将仅用来存储被加了星号和笔记的数据,这样搜索所有书籍的时候,不会弄乱数据库。
在第一部分中,我们只有一个类,这是我刻意为之的,因为代码并不是很多。而且把所有的代码都放在一个地方,对于那样的一个简单 App 来说,是很好的。
但是随着项目开始增长,必须要调整结构了。
我做了一些结构上的改进,例如:
Book 类越来越大,所以我将它抽成了一个单独的类。
使用 BookCard 来扩展 StatefulWidget。
class BookCard extends StatefulWidget {
BookCard(this.book);
final Book book;
@override
State<StatefulWidget> createState() => new BookCardState();
}
它需要通过 Book 对象来初始化。
class BookCardState extends State<BookCard> {
Book bookState;
@override
void initState() {
super.initState();
bookState = widget.book;
在 BookCardState 中,持有一个变量来存储 Book 对象,这是因为数据库中可能包含同一本书所提供的更多信息,并且用户可以在任何时间更新这些信息。
数据库
在 pubspec.yaml 文件中,添加了两个依赖项: Sqfilte 、 path_provider。
sqflite: any
path_provider: "^0.3.1"
代码如下:
class BookDatabase {
static final BookDatabase _bookDatabase = new BookDatabase._internal();
final String tableName = "Books";
Database db;
static BookDatabase get() {
return _bookDatabase;
}
BookDatabase._internal();
Future init() async {
// Get a location using path_provider
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "demo.db");
db = await openDatabase(path, version: 1,
onCreate: (Database db, int version) async {
// When creating the db, create the table
await db.execute(
"CREATE TABLE $tableName ("
"${Book.db_id} STRING PRIMARY KEY,"
"${Book.db_title} TEXT,"
"${Book.db_url} TEXT,"
"${Book.db_star} BIT,"
"${Book.db_notes} TEXT"
")");
});
}
数据库是一个单例模式,可以使用静态的 BookDatabase.get()
方法,获取到它。init()
方法用来将数据库正确的初始化(利用 path_provider),并创建必要的 SQLite 表。
/// Get a book by its id, if there is not entry for that ID, returns null.
Future<Book> getBook(String id) async{
var result = await db.rawQuery('SELECT * FROM $tableName WHERE ${Book.db_id} = "$id"');
if(result.length == 0)return null;
return new Book.fromMap(result[0]);
}
这里使用 db.rawQuery()
来查询数据,它是一个标准的 SQL 查询语句,在查询完成之后会返回一个 Map 集合。查询结果可能包含很多条数据,因为可能存在多个与搜索条件相匹配的查询结果,这些数据被存储在 Map<String,dynamic>
中,我们必须自己去解析它。
/// Inserts or replaces the book.
Future updateBook(Book book) async {
await db.inTransaction(() async {
await db.rawInsert(
'INSERT OR REPLACE INTO '
'$tableName(${Book.db_id}, ${Book.db_title}, ${Book.db_url}, ${Book.db_star}, ${Book.db_notes})'
' VALUES("${book.id}", "${book.title}", "${book.url}", ${book.starred? 1:0}, "${book.notes}")');
});
}
最后它将书籍数据,插入或者更新到数据库当中。
就是这样,很容易不是吗?
现在,数据库的功能已经封装完成,我们还需要在我们的项目中使用它。这在 Flutter 中,一切都变得简单。
@override
void initState() {
super.initState();
bookState = widget.book;
BookDatabase.get().getBook(widget.book.id)
.then((book){
if (book == null) return;
setState((){
bookState = book;
});
});
}
在获取到数据之后,我们遍历每条来自网络的书籍,是否同时也存在于数据库中,将匹配命中的数据用本地存储的书的内容替换掉。
child: new IconButton(
icon: bookState.starred? new Icon(Icons.star): new Icon(Icons.star_border),
color: Colors.black,
onPressed: (){
setState(() {
bookState.starred = !bookState.starred;
});
BookDatabase.get().updateBook(bookState);
},
),
当用户按下星号图标的时候,用新的信息更新数据库,并将图标做一个简单的动画,给用户一个反馈。
@override
void initState() {
super.initState();
_textController = new TextEditingController(text: widget.book.notes);
subject.stream.debounce(new Duration(milliseconds: 400)).listen((text){
widget.book.notes = text;
BookDatabase.get().updateBook(widget.book);
});
}
在 _BookNotesPageState
中,用户输入了内容之后,需要保存这些注释的笔记内容。那么有几种场景来触发它,可能有一个按钮来触发保存,也可以在离开页面的时候自动保存。
我这里使用了与我上一篇文章中相同的技术,一个 RxDart,在用户停止输入后 400 毫秒后,将信息存储到本地数据库中。
过度动画
class FadeRoute<T> extends MaterialPageRoute<T> {
FadeRoute({ WidgetBuilder builder, RouteSettings settings })
: super(builder: builder, settings: settings);
@override
Widget buildTransitions(BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
if (settings.isInitialRoute)
return child;
// Fades between routes. (If you don't want any animation,
// just return child.)
return new FadeTransition(opacity: animation, child: child);
}
}
我做了一个自己的页面路由,在页面之间,实现淡入淡出的动画。
onTap: (){
Navigator.of(context).push(
new FadeRoute(
builder: (BuildContext context) => new BookNotesPage(bookState),
settings: new RouteSettings(name: '/notes', isInitialRoute: false),
));
},
当用户点击一张书籍卡片的时候会被调用。使用的是 FadeTransition 动画。BookNotesPage 是用来显示当前是书籍的页面。
new Hero(
child: new Image.network(bookState.url),
tag: bookState.id,
):
最后,在每个页面上,Image 都被包装在一个 Hero
里面,它自己来负责元素动画。
小结
就是这样,现在该应用程序还可以支持收藏书籍,甚至我们可以写下自己的读书笔记。
如果你喜欢本文,记得点赞让我知道你喜欢它,有任何问题可以在留言区留言。
推荐阅读:
以上是关于Flutter:扩展上周的“书架” App,利用数据库来存储笔记和收藏!的主要内容,如果未能解决你的问题,请参考以下文章
Flutter:一小时从零构建一个简单的 App,以及你如何做到这一点!