Flutter入门实战之(一)商城首页界面(轮播图、分类导航、商品列表)

效果图:

首页

代码分析:

首先看一下整个首页界面需要导入的包文件。

import 'dart:core';  // dart包
import 'package:flutter/material.dart'; // flutter包,只有导入了它才可以使用各种flutter的各种Widget组件
import 'package:dio/dio.dart'; // 一个用于发送http请求的包(第三方插件)
import 'package:native_app/view/classify/index.dart'; // 分类页面(底部导航用到)
import 'package:native_app/view/find/list.dart'; // 发现页面(底部导航用到)
import 'package:native_app/view/user/cart.dart'; // 购物车页面(底部导航用到)
import 'package:native_app/view/user/index.dart'; // 个人中心页面(底部导航用到)

import 'package:flutter_swiper/flutter_swiper.dart'; // 轮播图(第三方插件)
import 'package:native_app/model/product.dart'; // 用于解析请求回来的商品数据JSON
import 'package:native_app/model/classify.dart'; // 用于解析请求回来的分类数据JSON
import '../../components/NavList.dart'; // 分类导航区域布局
import '../../components/ProductList.dart'; // 首页展示商品列表布局组件
import '../../config/web.config.dart'; // 接口配置文件
import '../../components/NavBottomItems.dart'; // 底部导航组件

import '../../router/application.dart'; // 路由配置
import '../../utils/utils.dart'; // 一个常用工具库

导入的包就不多说了,大家看注释也能看到是做什么用的。继续往下看。

可以看到定义了两个请求函数,作用也很明显,获取 “商品数据” 和 “获取分类列表”

// 获取商品列表
Future getProductList() async {
  try {
    var dio = Dio();
    var url = webApi['productList'];
    Response response = await dio.get("$url?page=1&limit=10");
    return response.data;
  } catch (e) {
    print(e);
  }
}

// 获取分类列表
Future getCategoryList() async {
  try {
    var url = webApi['categoryList'];
    Response response = await Dio().get(url);
    return response.data;
  } catch (e) {
    print(e);
  }
}

对这两个函数做做一下简单的说明,(其实不用解释大家都懂是吧....)
var dio = Dio(); // 实例化一个Dio对象,这个对象用来发送请求的
webApi['productList'] // 从我们上面的接口配置文件(web.config.dart)获取一个接口URL
Response response = await Dio().get(url); // 通过get方式获取数据

class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

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

对于上面的代码引用一下flutter中文网的一句话:

Stateful widgets 持有的状态可能在 widget 生命周期中发生变化,实现一个 stateful widget 至少需要两个类:
1、一个 StatefulWidget 类;
2、一个 State 类,StatefulWidget 类本身是不变的,但是 State 类在 widget 生命周期中始终存在。

class _HomePageState extends State<HomePage> {
  List<ProductData> productList = <ProductData>[]; // 商品列表
  List<NavList> navServeList = []; // 分类导航

定义两个数组用来保存 “商品列表数据” 和 “分类导航数据”

 // 分类导航列表
  Widget bodyGrid(List<NavList> menu) => SliverGrid(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 5,
          mainAxisSpacing: 0.0,
          crossAxisSpacing: 0.0,
          childAspectRatio: 0.9,
        ),
        delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
          var params = menu[index].name + menu[index].id.toString();
          return InkWell(
            onTap: () {
              Application.router.navigateTo(
                  context, "/productList/${Uri.encodeComponent(params)}");
            },
            child: NavList(
              name: menu[index].name,
              img: 'http://47.107.101.76/' + menu[index].img,
            ),
          );
        }, childCount: navServeList.length),
      );

首页轮播图下面的分类导航布局,
采用SliverGrid宫格Widget,每行显示5个,点击对应的分类跳转到分类商品列表页面,
值得注意的是,因为路由传过去的参数带有中文,所有需要Uri.encodeComponent()编码一下,不然会报错误。

// 商品列表
  Widget bodyProductList(List<ProductData> shop) => SliverGrid(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 1.0,
          crossAxisSpacing: 1.0,
          childAspectRatio: 0.7,
        ),
        delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
          return ProductList(
            id: shop[index].id,
            url: shop[index].thumbnail,
            price: shop[index].price,
            market: shop[index].orignPrice,
            name: shop[index].title,
          );
        }, childCount: productList.length),
      );

商品列表和分类导航一样都是采用SliverGrid宫格Widget

@override
  initState() {
    // 获取商品列表数据
    getProductList().then((data) {
      Product product = Product.fromJson(data);
      List<ProductData> showData = <ProductData>[];
      product.data.forEach((v) => {showData.add(v)});
      setState(() {
        productList = showData;
      });
    });
    // 获取分类列表数据
    getCategoryList().then((data) {
      Classify list = Classify.fromJson(data);
      List<NavList> showData = [];
      if (list.data.length <= 0) {
        return;
      }
      for (var i = 0; i < list.data.length; i++) {
        switch (i.toString()) {
          case '0':
            {
              showData.addAll(navTakeMap(3, list.data[i].children));
            }
            break;
          case '1':
            {
              showData.addAll(navTakeMap(2, list.data[i].children));
            }
            break;
          case '2':
            {
              showData.addAll(navTakeMap(2, list.data[i].children));
            }
            break;
          case '3':
            {
              showData.addAll(navTakeMap(2, list.data[i].children));
            }
            break;
          case '4':
            {
              showData.addAll(navTakeMap(1, list.data[i].children));
            }
            break;
        }
      }
      setState(() {
        navServeList = showData;
      });
    });
    super.initState();
  }

initState函数:
主要是进行数据获取以及解析JSON数据。

 Product product = Product.fromJson(data); // 解析商品接口返回是json数据

用到之前引入的(import 'package:native_app/model/product.dart'; )

Classify list = Classify.fromJson(data); // 解析分类接口返回是json数据

用到之前引入的(import 'package:native_app/model/classify.dart'; )

@override
  void dispose() {
    super.dispose();
  }

销毁资源,不解释

 // 轮播图数据
  var recommend = [
    "http://47.107.101.76/static/upload/1566643032.webp",
    "http://47.107.101.76/static/upload/1566644192.webp",
    "http://47.107.101.76/static/upload/1566643284.webp",
    "http://47.107.101.76/static/upload/1566642477.webp",
    "http://47.107.101.76/static/upload/1566645256.webp",
    "http://47.107.101.76/static/upload/1566642775.webp",
    "http://47.107.101.76/static/upload/1566642251.webp",
    "http://47.107.101.76/static/upload/1566643913.webp"
  ];

  int _selectedIndex = 0; // 底部导航索引
 // 底部导航点击事件
  void _onItemTapped(int index) {
    if(index == 0) {
      return;
    }
    if (index == 3) {
      Application.router.navigateTo(context, "/cart");
      return;
    }
    this.setState((){
      _selectedIndex = index;
    });
  }

目前轮播图数据都是写死的,后期会改成从接口获取,接口还没来得及写呢。。。
_onItemTapped是一个点击事件触发的函数,接收一个int 型的参数,
如果是0代表就是首页,直接return,如果是3的话,进行路由跳转至购物车,其他的就更新_selectedIndex的值就行。

// 首页布局
  Widget HomePageUI() {
    return Scaffold(
      appBar: AppBar(
        centerTitle: true,
        title: Text(
          "强野绿色生活",
          style: TextStyle(color: Colors.white),
        ),
      ),
      body: CustomScrollView(
        shrinkWrap: true,
        slivers: <Widget>[
          new SliverPadding(
            padding: const EdgeInsets.all(0.0),
            sliver: new SliverList(
                delegate: new SliverChildListDelegate(<Widget>[
              Container(
                color: Colors.white,
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.width - 100.0,
                margin: EdgeInsets.only(bottom: 10.0),
                child: new Swiper(
                  itemBuilder: (BuildContext context, int index) {
                    return new Image.network(
                      recommend[index],
                      fit: BoxFit.contain,
                    );
                  },
                  itemCount: recommend.length,
                  pagination: new SwiperPagination(
                      builder: DotSwiperPaginationBuilder(
                          size: 6.0, activeSize: 6.0, color: Colors.grey)),
                  autoplay: true,
                ),
              ),
            ])),
          ),
          new SliverPadding(
            padding: const EdgeInsets.all(0.0),
            sliver: new SliverList(
                delegate: new SliverChildListDelegate(<Widget>[
              Container(
                height: 10.0,
                color: Colors.white,
              )
            ])),
          ),
          bodyGrid(navServeList),
          new SliverPadding(
            padding: const EdgeInsets.all(0.0),
            sliver: new SliverList(
                delegate: new SliverChildListDelegate(<Widget>[
              Container(
                width: MediaQuery.of(context).size.width,
                margin: EdgeInsets.only(bottom: 10.0),
              ),
            ])),
          ),
          bodyProductList(productList),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: navBottomItems,
        currentIndex: _selectedIndex,
        onTap: _onItemTapped,
        type: BottomNavigationBarType.fixed,
      ),
    );
  }

上面代码就是首页的整个布局,很简单,不多说了。

@override
  Widget build(BuildContext context) {
    switch (this._selectedIndex.toString()) {
      case '0':
        {
         return HomePageUI();
        }
        break;
      case '1':
        {
         return ClassifyIndex();
        }
        break;
      case '2':
        {
         return FindPage();
        }
        break;
      case '3':
        {
         return CartPage();
        }
        break;
      case '4':
        {
         return UserPage();
        }
        break;
      default:
      {
       return HomePageUI();
      }
    }
  }

最后就是根据 "_selectedIndex"的值,渲染UI。完成

完整代码:

https://github.com/AntJavascript/flutter-shop/blob/master/lib/view/home/index.dart

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