Flutter实战之实现一个简单的新闻阅读器

11,065 阅读4分钟

Introduction

纸上谈兵终觉浅,绝知此事要躬行。

catsao(音:cat骚),我撸的第一个Flutter Application Demo,目前阶段它主要仿造目前主流的新闻资讯类app布局,实现了简单的新闻浏览。我还要继续丰富它,让它越来越sao。

Points

  • TabBar实现:
 @override
 Widget build(BuildContext context) {
   final List<Text> tabTexts = <Text>[
     new Text(
         StringResources.TAB_TOP_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_SH_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_GN_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_GJ_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_YL_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_TY_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_JS_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_KJ_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_CJ_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     ),
     new Text(
         StringResources.TAB_SS_CN,
         style: new TextStyle(
             fontSize: 20.0
         )
     )
   ];
   final List<Tab> tabs = [];
   for (int i = 0 ; i < tabTexts.length ; i++) {
     tabs.add(
       new Tab(
         child: tabTexts[i],
       )
     );
   }

   return new DefaultTabController(
       length: tabs.length,
       child: new Scaffold(
         appBar: new AppBar(
           title: new Text(
               widget.title
           ),
           bottom: new TabBar(
             isScrollable: true,
             tabs: tabs,

           ),
         ),
         body: new TabBarView(
             children: tabTexts.map((Text tab) {
               return new Center(
                 child: new NewsPage(
                   tabName: tab.data,
                 )
               );
             }).toList()
         ),
         drawer: new DrawerPage(),
       )
   );
 }

主要通过DefaultTabController + TabBar + TabBarView三个Widget配合实现类似Android中ViewPager + Indicator的效果。

build方法返回的最外层是一个DefaultTabController Widget,正如它的名字那样,它是一个控制器,用来控制TabBar和TabBarView的联动。要在界面上绘制一个TabBar和其对应的内容页使用TabBar和TabBarView两个Widget来实现的。

通过配置Scaffold的appbar参数为界面添加一个标题栏,再配置AppBar的bottom参数为标题栏添加一个显示在其底部的Widget,在这里传递给bottom参数的是一个TabBar Widget,所以在标题栏下方会显示一个TabBar。TabBar的每个Tab默认是固定位置的,这里配置其isScrollable参数为true使其可滚动,tabs参数配置TabBar中的每个Tab Widget。

通过配置Scaffold的body参数为其添加主内容界面,这里的内容界面需要配合TabBar的切换而切换,所以配置一个TabBarView Widget。最终通过外层的DefaultTabController使得TabBar和TabBarView联动起来。

关于实现类似效果的更多详细信息可参阅DefaultTabControllerTabBarTabBarView

  • Drawer实现:
	return new DefaultTabController(
       length: tabs.length,
       child: new Scaffold(
         appBar: new AppBar(
           title: new Text(
               widget.title
           ),
           bottom: new TabBar(
             isScrollable: true,
             tabs: tabs,

           ),
         ),
         body: new TabBarView(
             children: tabTexts.map((Text tab) {
               return new Center(
                 child: new NewsPage(
                   tabName: tab.data,
                 )
               );
             }).toList()
         ),
         drawer: new DrawerPage(),
       )
   );

Drawer的实现也很简单,Flutter都已经封装好了,直接配置Scaffold的drawer参数来实现一个Drawer界面。

  • 数据缓存:

利用shared_preferences和sqflite插件分别实现SharePreference和DB的数据本地缓存,在项目的pubspec.yaml中配置依赖的插件:

dependencies:

  ......
  sqflite: "^0.8.3"
  shared_preferences: "^0.4.0"
  ......
  

然后就可以在使用的地方import相应的package来使用对应功能。

  • Column问题

使用Column时会遇到一个there are children with non-zero flex but the vertical constraints are unbounded的异常,大致意思是说Column内部有flex值不为0的children,但是当前Column在垂直方向的高度约束是无限制的。 简单解释一下,Column的children默认flex值是0,可以理解为flex为0的组件占用的空间是静态的。当一个组件的flex值不为0时,意味着它会去占用父容器布局完其他flex为0的组件后剩下的空间,占用多少跟flex的值有关。这有点类似Android中LinearLayout的weight。所以这个异常的意思其实是说因为当前的Column的垂直方向的高度约束是没有任何限制的,此时内部如果有flex非0的children的话它无法确定父容器的剩余空间有多大。

当一个Column的children中有一个或多个Expanded或Flexible组件,并且这个Column嵌套在另一个Column或ListView的内部,就会遇到这个问题。

解决的思路其实就是检查为什么自己的Column此时在垂直方向的高度约束是无限制的,大部分情况下可能是由于当前Column嵌套在另一个Column或ListView等容器中导致的。

关于该问题的详细描述可以参阅官方文档

Effect

由于聚合数据只有获取新闻列表的接口(也可能我没找到单条新闻详情的接口),所以目前点击新闻的跳转是直接用浏览器打开了其对应的url。另外聚合数据获取新闻列表的接口貌似也没有支持分页参数...我还升级了聚合数据的高级会员,略坑。

Todo

  • 下拉刷新
  • 增加不同频道Route
  • 图片本地缓存

End

天气接口:和风天气。

新闻接口:聚合数据。

部分图标:Iconfont-阿里巴巴矢量图标库。

源码地址:Catsao

关于Flutter项目的编译和运行可以参考官方文档