Flutter 轮播组件 flutter_swiper 以及 Dio.dart请求包的使用

flutter_swiper和dio的引入

flutter_swiper组件作为第三方组件,我们需要通过在github上找到并通过pubspec.yaml文件进行引入安装,dio也同一样,在github中搜索dio就可以看到对应的文档

image.png

接下来,根据德阳旅游资讯网的移动端H5页面,我们开发一个app首页.大致效果如下:


image.png

页面包管理引入

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编译器中点击快速修复,编译器会给我们添加上这个包.

image.png

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)自身本来就是一个异步请求,所以我们这里需要用到asyncawait来等待请求的结果,再把数据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;
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容