flutter_swiper和dio的引入
flutter_swiper
组件作为第三方组件,我们需要通过在github上找到并通过pubspec.yaml
文件进行引入安装,dio也同一样,在github中搜索dio就可以看到对应的文档
接下来,根据德阳旅游资讯网的移动端H5页面,我们开发一个app首页.大致效果如下:
页面包管理引入
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
import 'package:dio/dio.dart';
dart:convert
我们要使用dio返回的数据类型,把String
类型转换成Map
类型,或者把Map
类型转换成String
类型需要使用到下面2个函数
json.decode()
- 把String
类型转换成Map
类型,方便我们后面对json
数据的使用
json.encode()
- 把Map
类型转换成String
类型
如果需要用到这2个函数进行转换,那么久需要引入dart:convert
包,如果不引入,我们也可以通过在vscode编译器中点击快速修复,编译器会给我们添加上这个包.
flutter/material.dart
这个没什么好特别介绍的,安卓界面ui组件库
flutter_swiper/flutter_swiper.dart
刚才在pubspec.yaml
文件中引入安装的flutter_swiper
组件.引入后,我们才可以使用SwiperWidget()
组件
dio/dio.dart
非常好用的请求组件,简单好用.具体参考github文档进行使用
建立入口函数及简单布局
根据前面的图,我们大致把页面分为3个部分
- 顶部banner轮播图
- 按钮导航区域
- 新闻列表区域
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter_swiper',
home: Scaffold(
appBar: AppBar(
title: Text('德阳旅游资讯网'),
),
body: Container(
child: Column(
children: <Widget>[
// - 第一部分:banner轮播区域
Container(
height: 200.0,
child: Padding(
padding: EdgeInsets.fromLTRB(0, 0.0, 0, 20.0),
child: SwiperWidget()),
),
// - 第二部分:导航按钮区域
Container(height: 150.0, child: IndexNav()),
//- 新闻列表区域
NewsList(),
],
),
),
),
);
}
}
轮播组件开发
这里我们由于使用了dio进行请求,数据需要异步渲染,定义的SwiperWidget
组件需要继承StatefulWidget
,定义一个List
类型的dataList
作为组件数据渲染的数据模型.获取数据之后通过setState()
函数让flutter的widget再次渲染
这里需要注意dataList
再获取数据之前是一个空的数组,所以渲染时做一个length
判断,在数据没有加载出来之前,我们使用一个文本加载中进行填充轮播位置,否则会出现轮播在初始渲染时显示不正常的情况.当然也可以使用其他图片的形式代替加载等待.
//- 轮播
class SwiperWidget extends StatefulWidget {
SwiperWidget({Key key}) : super(key: key);
@override
_SwiperWidgetState createState() => _SwiperWidgetState();
}
class _SwiperWidgetState extends State<SwiperWidget> {
String url =
'https://sczxw.hzcloud.daqsoft.com/frontrest/index';
var params = {
'method': 'bannerList',
'appkey': 'dygly',
'channelCode': 'zydy',
'row': 4,
'page':1,
'apppwd': 'daqsoft'
};
List dataList = [];
@override
void initState() {
super.initState();
getHttp(this.url, params).then((res) {
setState(() {
this.dataList = json.decode(res.toString())['data']['rows'];
});
});
}
@override
Widget build(BuildContext context) {
return this.dataList.length > 0
? Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
this.dataList[index]['path'],
fit: BoxFit.cover,
);
},
index: 0,
itemCount: this.dataList.length,
pagination: new SwiperPagination(),
// control: new SwiperControl(),
)
: Text('加载中...');
}
}
导航按钮区域
导航按钮由于内容是定死的,并不需要接口来配置.我们可以使用一个List<Map>
类型的变量来作为配置
final List navList = [
{
'id': 1,
'name': '乡村游',
'icon': Icons.nature_people,
'color': Colors.orange[300]
},
{
'id': 2,
'name': '景区景点',
'icon': Icons.filter_hdr,
'color': Colors.blue[300]
},
{
'id': 3,
'name': '酒店住宿',
'icon': Icons.local_hotel,
'color': Colors.red[300]
},
{
'id': 4,
'name': '特色美食',
'icon': Icons.local_dining,
'color': Colors.orange[300]
},
{
'id': 5,
'name': '特色购物',
'icon': Icons.shopping_cart,
'color': Colors.orange[300]
},
{'id': 6, 'name': '休闲娱乐', 'icon': Icons.rowing, 'color': Colors.green[300]},
{
'id': 7,
'name': '推荐线路',
'icon': Icons.call_split,
'color': Colors.lightGreenAccent[300]
},
{
'id': 8,
'name': '旅游攻略',
'icon': Icons.border_color,
'color': Colors.red[300]
},
];
有了导航按钮配置,接下来我们开始发开对应的导航按钮区域这个组件
我们通过GridView.builder
组件来构建一个网格布局来实现导航按钮区域,导航按钮区域的组件的完整代码如下:
//- 页面导航
class IndexNav extends StatelessWidget {
final List navList = [
{
'id': 1,
'name': '乡村游',
'icon': Icons.nature_people,
'color': Colors.orange[300]
},
{
'id': 2,
'name': '景区景点',
'icon': Icons.filter_hdr,
'color': Colors.blue[300]
},
{
'id': 3,
'name': '酒店住宿',
'icon': Icons.local_hotel,
'color': Colors.red[300]
},
{
'id': 4,
'name': '特色美食',
'icon': Icons.local_dining,
'color': Colors.orange[300]
},
{
'id': 5,
'name': '特色购物',
'icon': Icons.shopping_cart,
'color': Colors.orange[300]
},
{'id': 6, 'name': '休闲娱乐', 'icon': Icons.rowing, 'color': Colors.green[300]},
{
'id': 7,
'name': '推荐线路',
'icon': Icons.call_split,
'color': Colors.lightGreenAccent[300]
},
{
'id': 8,
'name': '旅游攻略',
'icon': Icons.border_color,
'color': Colors.red[300]
},
];
Widget _getNavListData(context, index) {
return Container(
child: Column(
children: <Widget>[
CircleAvatar(
backgroundColor: this.navList[index]['color'],
child: Icon(
this.navList[index]['icon'],
color: Colors.white,
),
),
SizedBox(height: 4.0),
Text(
this.navList[index]['name'],
style: TextStyle(fontSize: 12),
)
],
),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 20.0, //- 水平子Widget 之间间距
mainAxisSpacing: 20.0, //- 垂直子Widget 之间间距
crossAxisCount: 4, //- 一行的Widget 数量,
childAspectRatio: 1.2, //- 设置宽度和高度的比例
),
itemCount: this.navList.length,
itemBuilder: _getNavListData);
}
}
新闻列表区域
经过前面对dio
的使用,我们只需要读取接口,简单的对页面渲染.即可,只需要注意数据更新使用setState()
进行更新,才能够让flutter重新渲染页面.
//- 新闻列表
class NewsList extends StatefulWidget {
NewsList({Key key}) : super(key: key);
@override
_NewsListState createState() => _NewsListState();
}
class _NewsListState extends State<NewsList> {
var params = {
'method': 'newsList',
'appkey': 'dygly',
'channelCode': 'zydy',
'row': 4,
'page': 1,
'apppwd': 'daqsoft'
};
String url = 'https://sczxw.hzcloud.daqsoft.com/frontrest/welcome';
List newsList = [];
@override
void initState() {
// TODO: implement initState
super.initState();
getHttp(url, params).then((res) {
List tempList = json.decode(res.toString())['data']['rows'];
for(var i = 0; i < tempList.length; i++) {
tempList[i]['img'] = tempList[i]['imgs'].split(',');
}
setState(() {
this.newsList = tempList;
});
print(this.newsList);
});
}
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView(
children: this.newsList.map((val) {
return ListTile(
leading: Image.network(val['img'][0], width: 80.0, height: 40.0, fit:BoxFit.cover),
title: Text(val['titcont'], overflow: TextOverflow.ellipsis,maxLines: 2,style:TextStyle(fontSize: 14))
);
}).toList(),),
);
}
}
Dio网络请求
最后我们来简单对上面demo中的请求做一个讲解.
下面的代码是按照github上的demo进行修改的,主要是调用了Dio().get()
方法使用get请求,同时在里面传入2个参数,一个是url
(请求地址),一个是queryParameters
请求参数
//- 请求
getHttp(url, params) async {
try {
Response response = await Dio().get(url, queryParameters: params);
return response;
} catch (e) {
print(e);
return e;
}
}
这里需要注意的是dart
的异步语法,由于Dio().get(url,queryParameters)
自身本来就是一个异步请求,所以我们这里需要用到async
和await
来等待请求的结果,再把数据return出去.
调用的时候只需要:
getHttp(url, params).then((res) {})
该文里的内容完整代码如下:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
import 'package:dio/dio.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'flutter_swiper',
home: Scaffold(
appBar: AppBar(
title: Text('德阳旅游资讯网'),
),
body: Container(
child: Column(
children: <Widget>[
Container(
height: 200.0,
child: Padding(
padding: EdgeInsets.fromLTRB(0, 0.0, 0, 20.0),
child: SwiperWidget()),
),
Container(height: 150.0, child: IndexNav()),
NewsList(),
],
),
),
),
);
}
}
//- 轮播
class SwiperWidget extends StatefulWidget {
SwiperWidget({Key key}) : super(key: key);
@override
_SwiperWidgetState createState() => _SwiperWidgetState();
}
class _SwiperWidgetState extends State<SwiperWidget> {
String url =
'https://sczxw.hzcloud.daqsoft.com/frontrest/index';
var params = {
'method': 'bannerList',
'appkey': 'dygly',
'channelCode': 'zydy',
'row': 4,
'page':1,
'apppwd': 'daqsoft'
};
List dataList = [];
@override
void initState() {
super.initState();
getHttp(this.url, params).then((res) {
setState(() {
this.dataList = json.decode(res.toString())['data']['rows'];
});
});
}
@override
Widget build(BuildContext context) {
return this.dataList.length > 0
? Swiper(
itemBuilder: (BuildContext context, int index) {
return new Image.network(
this.dataList[index]['path'],
fit: BoxFit.cover,
);
},
index: 0,
itemCount: this.dataList.length,
pagination: new SwiperPagination(),
// control: new SwiperControl(),
)
: Text('加载中...');
}
}
//- 页面导航
class IndexNav extends StatelessWidget {
final List navList = [
{
'id': 1,
'name': '乡村游',
'icon': Icons.nature_people,
'color': Colors.orange[300]
},
{
'id': 2,
'name': '景区景点',
'icon': Icons.filter_hdr,
'color': Colors.blue[300]
},
{
'id': 3,
'name': '酒店住宿',
'icon': Icons.local_hotel,
'color': Colors.red[300]
},
{
'id': 4,
'name': '特色美食',
'icon': Icons.local_dining,
'color': Colors.orange[300]
},
{
'id': 5,
'name': '特色购物',
'icon': Icons.shopping_cart,
'color': Colors.orange[300]
},
{'id': 6, 'name': '休闲娱乐', 'icon': Icons.rowing, 'color': Colors.green[300]},
{
'id': 7,
'name': '推荐线路',
'icon': Icons.call_split,
'color': Colors.lightGreenAccent[300]
},
{
'id': 8,
'name': '旅游攻略',
'icon': Icons.border_color,
'color': Colors.red[300]
},
];
Widget _getNavListData(context, index) {
return Container(
child: Column(
children: <Widget>[
CircleAvatar(
backgroundColor: this.navList[index]['color'],
child: Icon(
this.navList[index]['icon'],
color: Colors.white,
),
),
SizedBox(height: 4.0),
Text(
this.navList[index]['name'],
style: TextStyle(fontSize: 12),
)
],
),
);
}
@override
Widget build(BuildContext context) {
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 20.0, //- 水平子Widget 之间间距
mainAxisSpacing: 20.0, //- 垂直子Widget 之间间距
crossAxisCount: 4, //- 一行的Widget 数量,
childAspectRatio: 1.2, //- 设置宽度和高度的比例
),
itemCount: this.navList.length,
itemBuilder: _getNavListData);
}
}
//- 新闻列表
class NewsList extends StatefulWidget {
NewsList({Key key}) : super(key: key);
@override
_NewsListState createState() => _NewsListState();
}
class _NewsListState extends State<NewsList> {
var params = {
'method': 'newsList',
'appkey': 'dygly',
'channelCode': 'zydy',
'row': 4,
'page': 1,
'apppwd': 'daqsoft'
};
String url = 'https://sczxw.hzcloud.daqsoft.com/frontrest/welcome';
List newsList = [];
@override
void initState() {
// TODO: implement initState
super.initState();
getHttp(url, params).then((res) {
List tempList = json.decode(res.toString())['data']['rows'];
for(var i = 0; i < tempList.length; i++) {
tempList[i]['img'] = tempList[i]['imgs'].split(',');
}
setState(() {
this.newsList = tempList;
});
print(this.newsList);
});
}
@override
Widget build(BuildContext context) {
return Expanded(
child: ListView(
children: this.newsList.map((val) {
return ListTile(
leading: Image.network(val['img'][0], width: 80.0, height: 40.0, fit:BoxFit.cover),
title: Text(val['titcont'], overflow: TextOverflow.ellipsis,maxLines: 2,style:TextStyle(fontSize: 14))
);
}).toList(),),
);
}
}
//- 请求
getHttp(url, params) async {
try {
Response response = await Dio().get(url, queryParameters: params);
return response;
} catch (e) {
print(e);
return e;
}
}