项目Github地址:a1203991686/CoolWeather_Flutter

在第六章中我们写了天气预报的页面, 但是你作为天气预报肯定能选择城市吧。所以我们现在来写选择省、市、区的界面。

我们使用的是郭霖大神在第一行代码最后面酷欧天气的API。

1. 实现界面

既然是一个选择省市区的界面,那么我们就用ListView

首先看一下大致界面:
Screenshot_1570967184

就直接使用ListViewbuilder()方法,忘了的同学可以看前面第5章。

view文件夹下新建一个类provinces_page.dart,接下来就在这个文件里面写代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class ProvincesPageWidget extends StatefulWidget {
ProvincesPageWidget({Key key}) : super(key: key);

@override
ProvincesPageStateWidget createState() => new ProvincesPageStateWidget();
}

class ProvincesPageStateWidget extends State<ProvincesPageWidget> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text(
"省份",
style: TextStyle(fontSize: 25.0),
),
),
body: ListView.builder(
itemCount: 30,
itemBuilder: (context, index) {
return ListTile(
title: Text("$index"),
);
},
),
);
}
}

和省一样。在view文件夹下新建一个类city_page.dart,接下来就在这个文件里面写代码。照着省的依葫芦画瓢,写一个ListView

和省一样。在view文件夹下新建一个类counties_page.dart,接下来就在这个文件里面写代码。照着省的依葫芦画瓢,写一个ListView

2. 网络请求

网络请求可以看下之前第7章的内容。主要是通过Dio进行网络请求。大家可以看下第7章网络请求的内容。

我们网络请求主要需要以下几个API:

  • 请求省份列表:http://guolin.tech/api/china
  • 请求对应市列表:http://guolin.tech/api/china/provinceID
  • 请求对应区县列表:http://guolin.tech/api/china/provinceID/cityID

由于我们在之前文章中使用的省作为例子,这块我们就用区县作为例子:

转为Dart类

首先使用网站https://caijinglong.github.io/json2dart/来讲Json数据转为Dart实体类:
Json

然后在项目lib目录新建一个文件夹。名为bean。这个文件夹主要存放实体类的代码。然后在这个文件夹下面新建一个dart文件,命名为Counties。然后把生成的Dart代码复制粘贴进去。但是你会发现复制进去后会报错,这个不用急,接下来我们来处理错误。

生成.g.dart文件

接下来在项目根目录的pubspec.yaml文件的dependencies项下面添加依赖json_annotationdev_dependencies项下面添加build_runnerjson_serializable

接着打开终端,定位到项目根目录,输入
flutter packages pub run build_runner build,运行即可。运行完毕你就会发现你项目的存实体类的lib/bean目录多了一个文件,名为Counties.g.dart。同时你Counties.dart里面的错误也没有了。

网络请求

最后你就可以通过Dio来进行网络请求了。

1
Dio().get(http://guolin.tech/api/china/$_provinceID/$_cityID);

那么到这有的同学可能就会问了,你网址里面有provinceIDcityID,那这两个怎么获取,这个时候就得用上带值路由跳转了。

我们一般都是这样的一个逻辑:先选择省份、再选择城市、最后选择区和县。所以我们这个CountyPageWidget肯定是由CityPageWidget调用的。那么我们可以在CityPageWidgetListView里面将Text通过GestureDetector包起来,然后将GestureDetectoronTap方法设置为跳转到CountyPageWidgetCityPageWidgetListView代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ListView.builder(
itemCount: _cityList.cities.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ListTile(
title: Text("${_cityList.cities[index].name}"),
),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return CountiesPageWidget(
provinceID: _provinceID, //由CityWidget的上一级ProvinceWidget传过来,
cityID: _cityList.cities[index].id, // 由网络请求回来的数据传入
);
}));
},
);
},
);

这样CountyPageWidget所需的provinceIDcityID都是由CityPageWidget传入的,那么我们怎么对他传入的值进行处理呢,怎么把它添加到网址里去?接下来大家看代码就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class CountyPageWidget extends StatefulWidget {
//在Widget中定义两个,方便由其他Widget传入
final int provinceID;
final int cityID;

// 设置构造方法,在构造方法中传入两个变量
CountyPageWidget({Key key, this.provinceID, this.cityID}) : super(key: key);

@override
CountyPageWidgetState createState() => CountyPageWidgetState();
}

class CountyPageWidgetState extends State<CountyPageWidget> {
// 在State中定义两个变量
int _provinceID;
int _cityID;
List<County> _county;

@override
void initState() {
// 在此将Widget中的两个变量赋值给State中的变量
_provinceID = widget.provinceID;
_cityID = widget.cityID;
super.initState();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
title: Text(
"区县",
style: TextStyle(fontSize: 25.0),
),
),
body: FutureBuilder( // UI异步更新组件
// 这块就可以调用了
future: Dio().get("http://guolin.tech/api/china/$_provinceID/$_cityID"),
builder: (BuildContext context, AsyncSnapshot snapshot) {
...省略后面所有代码
}

3. UI异步更新

与异步更新相关的知识大家也可以看我们之前的第7章博客,这块只讲应用。

将Scaffold的body参数设置为FutureBuilder,并在FutureBuilder里面调用ListView:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
FutureBuilder(
future: Dio().get("http://guolin.tech/api/china"),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Response response = snapshot.data;
//发生错误
if (snapshot.hasError) {
return Text(snapshot.error.toString());
}

provinceList = getProvinceList(response.data);
//请求成功,通过项目信息构建用于显示项目名称的ListView
return ListView.builder(
itemCount: provinceList.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ListTile(
title: Text("${provinceList[index].name}"),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return CityPageWidget(
provinceID: provinceList[index].id,
);
}));
},
);
},
);
}
// 请求未完成时弹出loading
return CircularProgressIndicator();
},
)

与省同理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
FutureBuilder(
future: Dio().get("http://guolin.tech/api/china/$_provinceID"),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Response response = snapshot.data;
//发生错误
if (snapshot.hasError) {
return Text(snapshot.error.toString());
}

_cityList = getCityList(response.data);
//请求成功,通过项目信息构建用于显示项目名称的ListView
return ListView.builder(
itemCount: _cityList.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ListTile(
title: Text("${_cityList[index].name}"),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return CountiesPageWidget(
provinceID: _provinceID,
cityID: _cityList[index].id,
);
}));
},
);
},
);
}
// 请求未完成时弹出loading
return CircularProgressIndicator();
},
)

区县

与省同理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
FutureBuilder(
future: Dio().get("http://guolin.tech/api/china/$_provinceID/$_cityID"),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Response response = snapshot.data;
//发生错误
if (snapshot.hasError) {
return Text(snapshot.error.toString());
}

_county = getCountyList(response.data);
//请求成功,通过项目信息构建用于显示项目名称的ListView
return ListView.builder(
itemCount: _county.length,
itemBuilder: (context, index) {
return GestureDetector(
child: ListTile(
title: Text("${_county[index].name}"),
),
onTap: () {
_saveCityID(_county[index].weatherId);
Navigator.push(context, MaterialPageRoute(builder: (context) {
return MainPage(
cityID: _county[index].weatherId,
);
}));
},
);
},
);
}
// 请求未完成时弹出loading
return CircularProgressIndicator();
},
)