1、tab与路由配置
在routers/router.dart中, 配置路由:
import 'dart:js';
import 'package:flutter/material.dart';
import 'package:newsmth/pages/test/tabs/tabs.dart';
// 配置路由
final routes = {
'/': (context) => const Tabs(),
'/search': (context) => const SearchPage(),
};
//固定写法
var onGenerateRoute = (RouteSettings settings) {
//统一处理
final String? name = settings.name;
final Function pageContentBuilder = routes[name] as Function;
if (pageContentBuilder != null) {
if (settings.arguments != null) {
final Route route = MaterialPageRoute(
builder: (context) =>
pageContentBuilder(context, arguments: settings.arguments));
return route;
} else {
final Route route =
MaterialPageRoute(builder: (context) => pageContentBuilder(context));
return route;
}
}
};
在tabs/tabs.dart中, 设置tab:
import 'package:flutter/material.dart';
import 'package:newsmth/pages/favorite/index.dart';
import 'package:newsmth/pages/home/index.dart';
import 'package:newsmth/pages/message/index.dart';
import 'package:newsmth/pages/my/index.dart';
class Tabs extends StatefulWidget {
const Tabs({Key? key}) : super(key: key);
@override
_TabsState createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
int _currentIndex = 0;
final List _pageList = [
const HomeView(),
const FavoriteView(),
const MessageView(),
const MyView(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("jdshop"),
),
body: _pageList[_currentIndex], //tab对应页面
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => {
setState(() {
_currentIndex = index;
})
},
type: BottomNavigationBarType.fixed, //此配置可以实现展示多个tab
fixedColor: Colors.red,//tab选中颜色
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "首页",
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label: "分类",
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart_outlined),
label: "购物车",
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
label: "我的",
)
],
),
);
}
}
在main.dart中使用Tab和路由配置:
import 'routers/router.dart';
...
class _myAppState extends State<MyApp>{
@override
Widget build(BuildContext context){
return MaterialApp(
initialRoute: '/', //初始路由
onGenerateRoute: onGenerateRoute
)
}
}
在homeView.dart中, 测试路由跳转:
RaisedButton(
child: Text("跳转到搜索"),
onPressed:(){
Navigator.pushName(context, '/search');
}
)
2、首页布局
使用插件flutter_swiper, 来实现轮播图
dependencies:
flutter_swiper: ^1.16 # 轮播图
flutter_screenutil: ^5.0.3 # 屏幕适配
在home.dart 中, 使用
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
class CartPage extends StatefulWidget {
const CartPage({Key? key}) : super(key: key);
@override
_CartPageState createState() => _CartPageState();
}
class _CartPageState extends State<CartPage> {
//轮播图
Widget _swiperWidget() {
List<Map> imgList = [
{"url": "https://www.ityiing.com/images/flutter/slide01.jpg"},
{"url": "https://www.ityiing.com/images/flutter/slide02.jpg"},
{"url": "https://www.ityiing.com/images/flutter/slide03.jpg"},
];
return Container(
child: AspectRatio(
aspectRatio: 2 / 1, //设置宽高比
child: Swiper(
itemBuilder: (BuildContext context, int index) {
return Image.network(
imgList[index]["url"],
fit: BoxFit.fill,
);
},
itemCount: imgList.length,
pagination: const SwiperPagination(),
control: const SwiperControl(),
autoplay: true, //自动轮播
),
),
);
}
Widget _titleWidget(value) {
return Container(
height: 34.h,
margin: EdgeInsets.only(left: 10.w),
padding: EdgeInsets.only(left: 10.w),
//设置左侧边框
decoration: BoxDecoration(
border: Border(
left: BorderSide(
color: Colors.red,
width: 10.w,
),
),
),
child: Text(
value,
style: const TextStyle(
color: Colors.black54,
),
),
);
}
@override
Widget build(BuildContext context) {
return ListView(
children: [
_swiperWidget(),
_titleWidget("猜你喜欢"),
SizedBox(height: 10.sp),
_titleWidget("热门推荐"),
SizedBox(height: 10.sp),
],
);
}
}
3、封装适配库以实现左右滑动Listview
首页-热门商品
//热门商品
Widget _hotProductList() {
return Container(
height: 240.h,
// width: double.infinity, //要占用整个宽度或高度
padding: EdgeInsets.all(20.w),
child: ListView.builder(
scrollDirection: Axis.horizontal, //水平滚动
itemBuilder: (context, index) {
return Column(
children: [
Container(
height: 140.h,
width: 140.w,
margin: EdgeInsets.only(right: 21.w),
child: Image.network(
"https://www.itying.com/images/flutter/hot${index + 1}.jpg",
fit: BoxFit.cover, //图片适配容器宽高
),
),
Container(
padding: EdgeInsets.only(top: 10.h),
child: Text("第$index条"),
height: 44.h,
)
],
);
},
itemCount: 80,
),
);
}
4、网格布局
首页商品列表
//推荐商品
Widget _recProductItemWidget() {
var itemWidth = (1.sw - 30) / 2;
//没设置高度, 高度会自适应
return Container(
padding: const EdgeInsets.all(5),
width: itemWidth,
decoration: BoxDecoration(
//边框
border: Border.all(
color: Colors.black12,
width: 1,
)),
child: Column(
children: [
SizedBox(
width: double.infinity,
child: AspectRatio(
aspectRatio: 1/1,//防止服务器返回图片宽度不一致,导致高度不一致
child: Image.network(
"https://www.itying.com/images/flutter/list1.jpg",
fit: BoxFit.cover, //图片适配容器宽高
),
),
),
Padding(
padding: EdgeInsets.only(top: 10.h),
child: const Text(
"2019夏季新款",
maxLines: 2,
overflow: TextOverflow.ellipsis, //超出限制...
style: TextStyle(color: Colors.black54),
),
),
Padding(
padding: EdgeInsets.only(top: 20.h),
//左右布局
child: Stack(
children: [
Align(
alignment: Alignment.centerLeft,
child: Text(
"¥188.0",
style: TextStyle(color: Colors.red, fontSize: 16.sp),
),
),
Align(
alignment: Alignment.centerRight,
child: Text(
"¥198.0",
style: TextStyle(
color: Colors.black54,
fontSize: 14.sp,
decoration: TextDecoration.lineThrough),//配置一个下划线
),
),
],
),
)
],
),
);
}
Widget _recProductListWidget() {
return Container(
padding: EdgeInsets.all(10),
child: Wrap(
runSpacing: 10, //主轴间距
spacing: 10, //副轴间距
children: [
_recProductItemWidget(),
],
),
);
}
5、JSON转对象
一、在线方式
1、JSON to Dart
2、quicktype (推荐)
二、插件工具 (推荐)
1、FlutterJsonBeanFactory
2、json_serializable 和 build_runner
7、商品分类页面布局--左右菜单
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class Category extends StatefulWidget {
Category({Key? key}) : super(key: key);
@override
State<Category> createState() => _CategoryState();
}
class _CategoryState extends State<Category> {
@override
Widget build(BuildContext context) {
var _selectedIndex = 0;
//计算左侧宽度
var leftWidth = 1.sw / 4;
//右侧每一项宽度=(总宽度-左测宽度-GridView外侧原生左右的Padding值-GridView中间的间距)/3
var rightItemWidth = (1.sw - leftWidth - 20 - 20) / 3;
rightItemWidth = rightItemWidth.w;
var rightItemHeight = rightItemWidth + 28.h;
return Row(children: [
//左侧视图
Container(
width: leftWidth,
height: double.infinity,
color: Colors.red,
child: ListView.builder(
itemCount: 18,
itemBuilder: (context, index) {
return Column(
children: [
// InkWell当做"按钮"组件用
InkWell(
onTap: () {
setState(() {
_selectedIndex = index;
});
},
child: Container(
width: double.infinity,
height: 84.h,
padding: EdgeInsets.only(top: 24.h),
child: Text("第$index条", textAlign: TextAlign.center),
color: _selectedIndex == index ? Colors.red : Colors.white,
),
),
const Divider(height: 1) //如果不设置高度, 默认高度是16
],
);
},
),
),
//右侧视图
Expanded(
flex: 1,
child: Container(
padding: EdgeInsets.all(10.h),
height: double.infinity,
color: const Color.fromRGBO(240, 240, 240, 0.9),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, //每行数量
childAspectRatio: rightItemWidth / rightItemHeight, //宽高比
crossAxisSpacing: 10, //纵轴间距
mainAxisSpacing: 10, //主轴间距
),
itemBuilder: (context, index) {
return Container(
child: Column(
children: [
AspectRatio(
aspectRatio: 1 / 1,
child: Image.network(
"https://www.itying.com/images/flutter/list1.jpg",
fit: BoxFit.cover,
),
),
Container(
height: 28.h,
child: Text("女装"),
)
],
),
);
},
itemCount: 18,
),
),
),
]);
}
}
9、底部Tab切换保持页面状态
①IndexedStack 保持页面状态
IndexedStack 和 Stack 一样,都是层布局控件, 可以在一个控件上面放置另一 个控件,但唯一不同的是 IndexedStack 在同一时刻只能显示子控件中的一个控 件,通过 Index 属性来设置显示的控件。
IndexedStack 来保持页面状态的优点就是配置简单。IndexedStack 保持页面状 态的缺点就是不方便单独控制每个页面的状态。
IndexedStack 用法:
Container(
width: double.infinity,
height: double.infinity,
child: new IndexedStack(
index: 0,
alignment: Alignment.center,
children: <Widget>[
Image.network(
"https://www.itying.com/images/flutter/list1.jpg",
fit: BoxFit.cover,
),
Image.network("https://www.itying.com/images/flutter/list2.jpg",
fit: BoxFit.cover)
],
),
);
注: IndexedStack会一次性加载所有页面, 不方便控制所有页面状态
②AutomaticKeepAliveClientMixin 保持页面状态 ✅
AutomaticKeepAliveClientMixin 结合 tab 切换保持页面状态相比 IndexedStack 而言配置起来稍 微有些复杂。它结合底部 BottomNavigationBar 保持页面状态的时候需要进行如下配置。
import 'package:flutter/material.dart';
import 'package:newsmth/pages/favorite/index.dart';
import 'package:newsmth/pages/home/index.dart';
import 'package:newsmth/pages/message/index.dart';
import 'package:newsmth/pages/my/index.dart';
class Tabs extends StatefulWidget {
const Tabs({Key? key}) : super(key: key);
@override
_TabsState createState() => _TabsState();
}
class _TabsState extends State<Tabs> {
//sp1.初始化PageController
late PageController _pageController;
int _currentIndex = 0;
@override
void initState() {
super.initState();
_pageController = PageController(initialPage: _currentIndex);
}
final List<Widget> _pageList = [
const HomeView(),
const FavoriteView(),
const MessageView(),
const MyView(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("jdshop"),
),
body: PageView(//sp2.设置tab对应页面
controller: _pageController,
children: _pageList,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => {
setState(() {
_currentIndex = index;
_pageController.jumpToPage(index);//sp3.设置页面切换
})
},
type: BottomNavigationBarType.fixed, //此配置可以实现展示多个tab
fixedColor: Colors.red, //选中颜色
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: "首页",
),
BottomNavigationBarItem(
icon: Icon(Icons.category),
label: "分类",
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart_outlined),
label: "购物车",
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
label: "我的",
)
],
),
);
}
}
需要持久化的页面加入如下代码:
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin { //sp5.继承AutomaticKeepAliveClientMixin 来保持状态
@override
bool get wantKeepAlive => true;//sp6. 返回true
}
10、商品列表布局
注意:如果Container里面加上decoration属性, 这个时候color属性必须放在BoxDecoration里面
class _ProductListPageState extends State<ProductListPage> {
Widget _productListWidget() {
return Container(
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(top: 80.h),
child: ListView.builder(
itemBuilder: (context, index) {
//每一个元素
return Column(
children: [
//左边图片
Container(
width: 180.w,
height: 180.h,
child: Image.network("图片地址"),
),
//右侧内容
Expanded(
flex: 1,
child: Container(
height: 180.h, //此时要设置高度, 否则子组件内容无法撑满容器
margin: EdgeInsets.only(left: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [],
),
))
],
);
},
itemCount: 10,
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("商品列表"),
),
body: _productListWidget()
);
}
}
11、商品列表页面二级筛选导航布局
自定义Tab导航:
Widget _subHeaderWidget() {
return Positioned(
top: 0,
height: 80.h,
width: 750.w,
child: Container(
height: 80.h,
width: 750.w,
color: Colors.red,
child: Row(
children: [
Expanded(
flex: 1,
child: InkWell(
child: Padding(
padding: EdgeInsets.fromLTRB(0, 16.h, 0, 16.h),
child: Text("综合", textAlign: TextAlign.center),
),
onTap: () {},
),
),
Expanded(
flex: 1,
child: InkWell(
child: Padding(
padding: EdgeInsets.fromLTRB(0, 16.h, 0, 16.h),
child: Text("价格", textAlign: TextAlign.center),
),
onTap: () {},
),
),
Expanded(
flex: 1,
child: InkWell(
child: Padding(
padding: EdgeInsets.fromLTRB(0, 16.h, 0, 16.h),
child: Text("筛选", textAlign: TextAlign.center),
),
onTap: () {},
),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("商品列表"),
),
body: Stack(
children: [
_productListWidget(),
_subHeaderWidget(),
],
),
);
}
实现筛选功能--侧边栏弹框:
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>(); //sp1
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,//sp2
appBar: AppBar(
title: Text("商品列表"),
actions: [
Text("")//sp4.去掉导航栏默认添加的调出侧边栏按钮
],
),
endDrawer: Drawer(//sp3.侧边栏弹框
child: Container(
child: Text("侧边栏弹框内容布局"),
),
),
body: Stack(
children: [
_productListWidget(),
_subHeaderWidget(),
],
),
);
}
通过事件打开侧边栏
InkWell(
onTap: () {
//注意:新版本的 Flutter 中 ScaffoldState? 为可空类型 注意判断
if(_scaffoldKey.currentState!=null){
_scaffoldKey.currentState!.openEndDrawer();
},
child: Text("筛选", textAlign: TextAlign.center),
),
12、上拉刷新监听
class _ProductListPageState extends State<ProductListPage> {
Widget _productListWidget() {
return Container(
padding: EdgeInsets.all(10),
margin: EdgeInsets.only(top: 80.h),
child: ListView.builder(
controller: _scrollController, //sp1.
...
}
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
//sp2.用于上拉分页
ScrollController _scrollController = ScrollController(); //listview的控制器
//分页
int _page = 1;
//数据
List _productList = [];
//排序
String _sort = "";
//解决重复请求的问题
bool flag = true;
@override
void initState() {
// TODO: implement initState
super.initState();
_scrollController.addListener(() {//sp3.监听滚动
//_scrollController.position.pixels //获取滚动条滚动高度
//_scrollController.position.maxScrollExtent //获取页面高度
if (_scrollController.position.pixels >
_scrollController.position.maxScrollExtent - 20) {
if (flag) {
_getProductListData();
}
}
});
}
}
注: _scrollController.jumpTo(0); //回到顶部