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