一个用Flutter写的阅读器

一个用Flutter写的阅读器

Posted by linxu on October 19, 2018

一个用Flutter写的阅读器

  项目代码我放到了GitHub上,项目地址   文末有效果图

  学习Flutter也有段时间了,但是一直没有想到用它来写个什么,这次有机会就直接用它来写了一个阅读工具。Flutter的学习建议大家花点时间,先了解一下Dart语言,浏览它的语法、风格,这样不至于在学习Flutter的过程中出现代码语法或风格的一些不理解的地方。

  关于Flutter的安装,我之前写过一个记录:Flutter之安装与体验,Windows环境的,希望能对大家有所帮助。

  为啥写这个应用,源于国庆去西安玩了一趟,深感那边大唐文化氛围的浓郁。回来之后想要了解一些唐朝过去的历史。然后在网上找到了黎东方先生的 细说历史系列丛书,但是都是txt格式的,不能方便的在手机上看,所以想着写一个读书工具。于是有了这个应用工具,我起了个名字叫:阅简阁。希望以后有更多的书能放上去。现在上面可以看的有:

1.细说秦汉
2.细说三国
3.细说两晋南北朝
4.细说隋唐
5.细说宋朝
6.细说明朝
7.细说清朝
8.细说民国初创
9.细说抗战

技术

相关技术网站:
  1.Flutter中文网
  2.Flutter官网

一、文件加载

  这个阅读应用读取的是txt文件,本来按照写Android的思路是直接在res目录下创建资源目录,然后用Java的文件读写操作去读取文件内容的。但是在Flutter当中,按照Flutter存放资源位置规则,我在根目录下新建txt目录,把文件放进去,却是没办法拿到这个资源目录。查看官网文档发现Flutter在代码中获取文件目录现在支持使用PathProvider 插件的两个目录:

临时目录: 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir()返回的值。 文档目录: 应用程序的目录,用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。

  一直没找到方法通过代码找到资源文件的目录,然后以文件读取的方式读取文件。所以文件内容的加载只能采用assets静态资源加载。这种加载方式很简单:

第一步、在pubspec.yaml文件声明好资源位置:

 assets:
        - txt/qinhan.txt

第二步、在代码中使用rootBundle.loadString(path)加载文件即可。assets静态资源加载详情

代码如下:

  Future<String> _loadString() async {
    //异步加载文件数据,返回一个String
    return await rootBundle.loadString(_path);
  }

   @override
  void initState() {
    super.initState();
    _loadString().then((String contents) {
      setState(() {
        List<String> list = contents.split('*****');

        list.forEach((String content) {
          widgets.add(_getTextView(content));//段落内容
          widgets.add(new Divider());//添加分割线
        });
      });
    });
  }

  获取到文章内容之后,我将文章切割成几个段落,然后放到ListView中,方便滑动显示。then方法是异步操作Future的一个方法,它在等待await操作返回之后执行。

二、ListView

  这个项目由两个地方使用到了ListView:第一处:显示文章名字;第二处,显示文章内容;   因为文章是一次性加载出来,为了能够更好的滑动,这里需要使用到ListView。这里我将文件分割成了多个段落,切割之后,放到再ListView里面。   以第一处为例,Flutter创建可以无限滑动的ListView的方法:

   new ListView.builder(
          padding: const EdgeInsets.all(16.0),
          itemCount: 2*bookInfo.length,
          itemBuilder: (context, i) {
            if (i >= 2*bookInfo.length) {
              return null;
            }
            if(i%2 == 0) {  // 偶数行显示书名
              return _buildRow(i~/2);
            }else{          // 奇数行显示分割线
              return new Divider();
            }
          }),
    );

  padding是ListView的内边距,itemBuilder是创建ListView的每个item。itemCount指明了这个ListView有多少个item,这里使用2*bookInfo.length的原因是我想在每个item之间添加分割线。ListView的item之间的分割线很好实现,新建一个Divider 的实例就可以了。

  下面看ListView的item创建方法:

 final _isFavorite = _favoriteBooks.contains(bookMap['title']);
    new ListTile(
      title: new Row(
        children: <Widget>[
          new Icon(Icons.book),//这是书名左边icon
          new Text(//书名
            bookMap['title'],
            textDirection: TextDirection.ltr,
            style: new TextStyle(fontSize: 16.0),
          )
        ],
      ),
      trailing: new IconButton(//右边的IconButton
          icon: new Icon(
            _isFavorite ? Icons.favorite : Icons.favorite_border,
            color: _isFavorite ? Colors.red : null,
          ),
          onPressed: _buttonOnPressed
      ),
      onTap: _pushBook,
    );

  ListTile是专门用于创建ListView的item的类,它的属性有:   title 用了一个Row用于在左边显示一个书本的Icon 和一个书名。   trailing 是右边显示一个IconButton 是一个爱心图标,它的样式根据用户的点击行为而改变(点击之后会把这本书书名加入到_favoriteBooks中)。onPressed 是这个IconButton的点击事件。   onTap 是ListView的item的点击响应事件。

  下面看_buttonOnPressed函数,它由IconButton点击事件触发:

// 按钮点击函数
    _buttonOnPressed() {
      setState(() {
        if (_isFavorite) {
          _favoriteBooks.remove(bookMap['title']);
        } else {
          _favoriteBooks.add(bookMap['title']);
        }
      });
    }

  这个函数根据用户点击行为,将特定的书加入到_favoriteBooks,而在现实样式的时候,根据这个IconButton对应的书本是否在_favoriteBooks中,作出相应动作。再看 setState 方法,这个方法调用会为State对象触发build()方法,从而对UI进行更新。

  再看item的点击函数_pushBook:

// 跳转函数
    _pushBook() {
      Navigator.of(context)
          .push(new MaterialPageRoute(builder: (context) {
        return new LoadData(bookMap['title'], bookMap['path']);
      }));
    }

  这个函数是响应item的点击事件,点击item的时候,跳转到另一个页面,在这个新的页面显示书本的内容。这个跳转就对应于Android的activity跳转。下面是Flutter中文网对页面跳转的介绍。这里return的时候直接new了一个另一个页面的实例,传递参数在构造函数中传递,比Intent方便一些。

Route和 Navigator。 一个route是一个屏幕或页面的抽象,Navigator是管理route的Widget。Navigator可以通过route入栈和出栈来实现页面之间的跳转。

最后的效果

iPhone

iPhone效果      iPhone效果

Android

Android效果