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); //回到頂部