> 文章列表 > Flutter 第一个界面

Flutter 第一个界面

Flutter 第一个界面

第一个页面

app首页

入口函数

一个Flutter工程的入口函数与Dart命令行工程一样是main,不同的是在Flutter中执行runApp(ArticleApp()) 就能够在手机屏幕上展示这个Widget。

import 'package:flutter/material.dart';
void main() => runApp(new ArticleApp());

ArticleApp

我们要实现的文章列表页面UI就在ArticleApp中定义:

class ArticleApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return new MaterialApp(home: new Scaffold(appBar: new AppBar(title: new Text('文章',style: const TextStyle(color: Colors.white),),),body: new ArticlePage(),),);}
}

build方法中返回的就是我们需要显示在屏幕上的widget。MaterialApp代表使用Material Design风格,这是一个封装了很多Android MD设计所必须要的组件的小部件。假设我们需要显示一个Text,而没有包裹在MaterialApp内:

class ArticleApp extends StatelessWidget {@overrideWidget build(BuildContext context) {//Center:摆放在中间return Center(child: Text("你好!"),);}
}

如果直接运行则会出现异常,因为Flutter不知道以什么顺序摆放文字(从左到右/从右到左)

因此我们不得不给Text指名textDirection属性:

class ArticleApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return Center(child: Text("你好!",style: const TextStyle(color: Colors.white),textDirection: TextDirection.ltr),);}
}

但是如果包含在MaterialApp当中我们就不需要指名类似textDirection这样的属性了,因为内部已经内置了一套风格,指明了这些必须属性。而Scaffold则实现了基本的 Material Design布局结构,在 Material 设计中定义的单个界面上的各种布局元素,在 Scaffold 中都支持。比如:AppBar、抽屉菜单、BottomNavigationBar等等。

ArticlePage

​ 在我们的布局中指定了Scaffold的body(主体)为ArticlePage,这是一个我们自定义的组合Widget。

class ArticlePage extends StatefulWidget {@override_ArticlePageState createState() => _ArticlePageState();
}class _ArticlePageState extends State<ArticlePage> {///滑动控制器ScrollController _controller = new ScrollController();///控制小菊花的显示bool _isLoading = true;///请求到的文章数据List articles = [];///banner图List banners = [];///总文章数有多少var listTotalSize = 0;///分页加载,当前页码var curPage = 0;@overridevoid initState() {super.initState();_controller.addListener(() {///获得 SrollController 监听控件可以滚动的最大范围var maxScroll = _controller.position.maxScrollExtent;///获得当前位置的像素值var pixels = _controller.position.pixels;///当前滑动位置到达底部,同时还有更多数据if (maxScroll == pixels && articles.length < listTotalSize) {///加载更多_getArticlelist();}});_pullToRefresh();}_getArticlelist([bool update = true]) async {/// 请求成功是map,失败是nullvar data = await Api.getArticleList(curPage);if (data != null) {var map = data['data'];var datas = map['datas'];///文章总数listTotalSize = map["total"];if (curPage == 0) {articles.clear();}curPage++;articles.addAll(datas);///更新uiif (update) {setState(() {});}}}_getBanner([bool update = true]) async {var data = await Api.getBanner();if (data != null) {banners.clear();banners.addAll(data['data']);if (update) {setState(() {});}}}///下拉刷新Future<void> _pullToRefresh() async {curPage = 0;Iterable<Future> futures = [_getArticlelist(), _getBanner()];await Future.wait(futures);_isLoading = false;setState(() {});return null;}@overrideWidget build(BuildContext context) {return Stack(children: <Widget>[///小菊花Offstage(offstage: !_isLoading, //是否隐藏child: new Center(child: CircularProgressIndicator()),),///内容Offstage(offstage: _isLoading,child: new RefreshIndicator(child: ListView.builder(itemCount: articles.length + 1,itemBuilder: (context, i) => _buildItem(i),controller: _controller,),onRefresh: _pullToRefresh),)],);}Widget _buildItem(int i) {if (i == 0) {return new Container(height: 180.0,child: _bannerView(),);}var itemData = articles[i - 1];return new ArticleItem(itemData);}Widget _bannerView() {var list = banners.map((item) {return Image.network(item['imagePath'], fit: BoxFit.cover);}).toList();return list.isNotEmpty? BannerView(list,intervalDuration: const Duration(seconds: 3),): null;}
}

这个Widget的代码比较多,它配置了我们见到的banner、与文章列表。代码中重写了State的生命周期方法initStatebuild。我们首先来观察build方法:

@overrideWidget build(BuildContext context) {//Stack:帧布局  return Stack(children: <Widget>[///正在加载Offstage( //可以控制是否隐藏offstage: !_isLoading, //是否隐藏child: new Center(child: CircularProgressIndicator()),//圆形进度指示器(小菊花)),///内容Offstage(offstage: _isLoading,child: new RefreshIndicator( //下拉刷新child: ListView.builder(itemCount: articles.length + 1, //列表视图的个数itemBuilder: (context, i) => _buildItem(i),//类似adapter,item显示什么?返回widgetcontroller: _controller,//滑动控制器),onRefresh: _pullToRefresh),//刷新回调方法)],);}

这段代码中各个部分都给到了注释,_buildItem_pullToRefresh方法分别用于条目视图的生成与新数据的获取。

_pullToRefresh

_pullToRefresh是传递给下拉刷新组件:RefreshIndicator的刷新回调方法参数,它需要返回一个Future<void>,同时我们初次进入页面也需要自动的去获取一次数据,所以我们还会在initState方法中主动的调用一次该方法。

Future<void> _pullToRefresh() async {curPage = 0;Iterable<Future> futures = [_getArticlelist(), _getBanner()];await Future.wait(futures);_isLoading = false;setState(() {});return null;}

在这个方法中,我们需要重新请求文章列表与banner图,因此借助Future.wait组合两个任务,在两个任务都完成后,再利用setState更新UI完成重绘。

_buildItem

获取到数据之后,接下来我们需要对这些数据进行展示

Widget _buildItem(int i) {if (i == 0) {return new Container(height: 180.0,child: _bannerView(),);}var itemData = articles[i - 1];return new ArticleItem(itemData);}Widget _bannerView() {///banners是请求到的banner信息组,其中imagePath代表了图片地址///map意为映射,对banners中的数据进行遍历并返回Iterable<?>迭代器,///?则是在map的参数:一个匿名方法中返回的类型var list = banners.map((item) {return Image.network(item['imagePath'], fit: BoxFit.cover);}).toList();///BannerView的条目不能为空return list.isNotEmpty? BannerView(list,///切换时间intervalDuration: const Duration(seconds: 3),): null;}

_buildItem用于生成ListView当中的条目。注意在配置ListView时,我们给的itemCount为:articles.length + 1。articles就是请求到的文章信息数量,而+1则是为了显示banner。因此当i=0,显示第一个条目时候,我们返回了一个BannerView。这个BannerView其实是一个库(关于如何导入第三方库在最后)。而ArticleItem则又是我们自己定义的用于显示文章信息item的组合Widget。

ArticleItem

class ArticleItem extends StatelessWidget {final itemData;const ArticleItem(this.itemData);@overrideWidget build(BuildContext context) {///时间与作者Row author = new Row( //水平线性布局children: <Widget>[//expanded 最后摆我,相当于linearlayout的weight权重new Expanded(child: Text.rich(TextSpan(children: [TextSpan(text: "作者: "),TextSpan(text: itemData['author'],style: new TextStyle(color: Theme.of(context).primaryColor))]))),new Text(itemData['niceDate'])//时间],);///标题Text title = new Text(itemData['title'],style: new TextStyle(fontSize: 16.0, color: Colors.black),textAlign: TextAlign.left,);///章节名Text chapterName = new Text(itemData['chapterName'],style: new TextStyle(color: Theme.of(context).primaryColor));Column column = new Column( //垂直线性布局crossAxisAlignment: CrossAxisAlignment.start, //子控件左对齐children: <Widget>[new Padding(padding: EdgeInsets.all(10.0),child: author,),new Padding(padding: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0),child: title,),new Padding(padding: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 10.0),child: chapterName,),],);return new Card(///阴影效果elevation: 4.0,child: column,);}
}

Expanded

可以按比例“扩伸”Row、Column所占用的空间。

const Expanded({int flex = 1, @required Widget child,
})

flex为弹性系数,和Android中的LinearLayoutweight比重效果一致。

class _LayoutWidgetState extends State<LayoutWidget> {@overrideWidget build(BuildContext context) {return Row(//将Row 分成 2+3+1分,children: <Widget>[Expanded(flex:2,child: Container(child: Text('1'), color: Colors.red)),Expanded(flex:3,child: Container(child: Text('1'), color: Colors.blue)),Expanded(flex:1,child: Container(child: Text('1'), color: Colors.yellow)),],);}
}

网络请求

​ 一个app中,网络请求是最基本的功能,我们需要使用网络请求数据用于显示或者进行不同的逻辑处理。在我们的案例中,同样需要请求文章数据与banner数据。在Dart SDK中的io库其实提供了HttpClient 进行网络请求。大家都知道,Java中也提供了HttpConnection,但是我们更喜欢使用更加方便的OkHttp,所以一般开发中,我们可能使用一些更加方便的网络库,比如http、dio等等。进入https://pub.dartlang.org/ 输入库名就能够搜索到相关的库。这次我们使用dio来完成网络的请求:

class HttpManager {Dio _dio;static HttpManager _instance;factory HttpManager.getInstance() {if (null == _instance) {_instance = new HttpManager._internal();}return _instance;}//以 _ 开头的函数、变量无法在库外使用HttpManager._internal() {///基础配置BaseOptions options = new BaseOptions(baseUrl: Api.baseUrl, //基础地址connectTimeout: 5000, //连接服务器超时时间,单位是毫秒receiveTimeout: 3000, //读取超时);_dio = new Dio(options);}request(url, {String method = "get"}) async {try {///默认使用get请求Options option = new Options(method: method);Response response = await _dio.request(url, options: option);///一般来说,提供的是json字符串,response.data得到的就是这个json对应的mapreturn response.data;} catch (e) {return null;}}
}

导入库

​ 在Flutter工程中存在一个pubspec.yaml文件。此文件类似build.gradle,在这个文件中进行我们整个工程的一些配置,其中就包括了库的导入。配置完成之后,点击右上角的Packages get就能自动下载依赖。

附上:

Dart packagesPub is the package manager for the Dart programming language, containing reusable libraries & packages for Flutter and general Dart programs.https://pub.flutter-io.cn/