flutterflutter

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

2019-11-07  本文已影响0人  vue爱好者

效果图:

首页

代码分析:

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

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

上一篇下一篇

猜你喜欢

热点阅读